helpers.go

  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	Title            string
 44	TitleColor       color.Color
 45	Description      string
 46	DescriptionColor color.Color
 47}
 48
 49func Status(ops StatusOpts, width int) string {
 50	t := styles.CurrentTheme()
 51	icon := "●"
 52	iconColor := t.Success
 53	if ops.Icon != "" {
 54		icon = ops.Icon
 55	}
 56	if ops.IconColor != nil {
 57		iconColor = ops.IconColor
 58	}
 59	title := ops.Title
 60	titleColor := t.FgMuted
 61	if ops.TitleColor != nil {
 62		titleColor = ops.TitleColor
 63	}
 64	description := ops.Description
 65	descriptionColor := t.FgSubtle
 66	if ops.DescriptionColor != nil {
 67		descriptionColor = ops.DescriptionColor
 68	}
 69	icon = t.S().Base.Foreground(iconColor).Render(icon)
 70	title = t.S().Base.Foreground(titleColor).Render(title)
 71	if description != "" {
 72		description = ansi.Truncate(description, width-lipgloss.Width(icon)-lipgloss.Width(title)-2, "…")
 73	}
 74	description = t.S().Base.Foreground(descriptionColor).Render(description)
 75	return strings.Join([]string{
 76		icon,
 77		title,
 78		description,
 79	}, " ")
 80}
 81
 82type ButtonOpts struct {
 83	Text           string
 84	UnderlineIndex int  // Index of character to underline (0-based)
 85	Selected       bool // Whether this button is selected
 86}
 87
 88// SelectableButton creates a button with an underlined character and selection state
 89func SelectableButton(opts ButtonOpts) string {
 90	t := styles.CurrentTheme()
 91
 92	// Base style for the button
 93	buttonStyle := t.S().Text
 94
 95	// Apply selection styling
 96	if opts.Selected {
 97		buttonStyle = buttonStyle.Foreground(t.White).Background(t.Secondary)
 98	} else {
 99		buttonStyle = buttonStyle.Background(t.BgSubtle)
100	}
101
102	// Create the button text with underlined character
103	text := opts.Text
104	if opts.UnderlineIndex >= 0 && opts.UnderlineIndex < len(text) {
105		before := text[:opts.UnderlineIndex]
106		underlined := text[opts.UnderlineIndex : opts.UnderlineIndex+1]
107		after := text[opts.UnderlineIndex+1:]
108
109		message := buttonStyle.Render(before) +
110			buttonStyle.Underline(true).Render(underlined) +
111			buttonStyle.Render(after)
112
113		return buttonStyle.Padding(0, 2).Render(message)
114	}
115
116	// Fallback if no underline index specified
117	return buttonStyle.Padding(0, 2).Render(text)
118}
119
120// SelectableButtons creates a horizontal row of selectable buttons
121func SelectableButtons(buttons []ButtonOpts, spacing string) string {
122	if spacing == "" {
123		spacing = "  "
124	}
125
126	var parts []string
127	for i, button := range buttons {
128		parts = append(parts, SelectableButton(button))
129		if i < len(buttons)-1 {
130			parts = append(parts, spacing)
131		}
132	}
133
134	return lipgloss.JoinHorizontal(lipgloss.Left, parts...)
135}
136
137func DiffFormatter() *diffview.DiffView {
138	formatDiff := diffview.New()
139	style := chroma.MustNewStyle("crush", styles.GetChromaTheme())
140	diff := formatDiff.
141		SyntaxHightlight(true).
142		ChromaStyle(style)
143	return diff
144}