| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- package core
- import (
- "image/color"
- "strings"
- "github.com/alecthomas/chroma/v2"
- "github.com/charmbracelet/bubbles/v2/help"
- "github.com/charmbracelet/bubbles/v2/key"
- "github.com/charmbracelet/crush/internal/tui/exp/diffview"
- "github.com/charmbracelet/crush/internal/tui/styles"
- "github.com/charmbracelet/lipgloss/v2"
- "github.com/charmbracelet/x/ansi"
- )
- type KeyMapHelp interface {
- Help() help.KeyMap
- }
- type simpleHelp struct {
- shortList []key.Binding
- fullList [][]key.Binding
- }
- func NewSimpleHelp(shortList []key.Binding, fullList [][]key.Binding) help.KeyMap {
- return &simpleHelp{
- shortList: shortList,
- fullList: fullList,
- }
- }
- // FullHelp implements help.KeyMap.
- func (s *simpleHelp) FullHelp() [][]key.Binding {
- return s.fullList
- }
- // ShortHelp implements help.KeyMap.
- func (s *simpleHelp) ShortHelp() []key.Binding {
- return s.shortList
- }
- func Section(text string, width int) string {
- t := styles.CurrentTheme()
- char := "─"
- length := lipgloss.Width(text) + 1
- remainingWidth := width - length
- lineStyle := t.S().Base.Foreground(t.Border)
- if remainingWidth > 0 {
- text = text + " " + lineStyle.Render(strings.Repeat(char, remainingWidth))
- }
- return text
- }
- func SectionWithInfo(text string, width int, info string) string {
- t := styles.CurrentTheme()
- char := "─"
- length := lipgloss.Width(text) + 1
- remainingWidth := width - length
- if info != "" {
- remainingWidth -= lipgloss.Width(info) + 1 // 1 for the space before info
- }
- lineStyle := t.S().Base.Foreground(t.Border)
- if remainingWidth > 0 {
- text = text + " " + lineStyle.Render(strings.Repeat(char, remainingWidth)) + " " + info
- }
- return text
- }
- func Title(title string, width int) string {
- t := styles.CurrentTheme()
- char := "╱"
- length := lipgloss.Width(title) + 1
- remainingWidth := width - length
- titleStyle := t.S().Base.Foreground(t.Primary)
- if remainingWidth > 0 {
- lines := strings.Repeat(char, remainingWidth)
- lines = styles.ApplyForegroundGrad(lines, t.Primary, t.Secondary)
- title = titleStyle.Render(title) + " " + lines
- }
- return title
- }
- type StatusOpts struct {
- Icon string // if empty no icon will be shown
- Title string
- TitleColor color.Color
- Description string
- DescriptionColor color.Color
- ExtraContent string // additional content to append after the description
- }
- func Status(opts StatusOpts, width int) string {
- t := styles.CurrentTheme()
- icon := opts.Icon
- title := opts.Title
- titleColor := t.FgMuted
- if opts.TitleColor != nil {
- titleColor = opts.TitleColor
- }
- description := opts.Description
- descriptionColor := t.FgSubtle
- if opts.DescriptionColor != nil {
- descriptionColor = opts.DescriptionColor
- }
- title = t.S().Base.Foreground(titleColor).Render(title)
- if description != "" {
- extraContentWidth := lipgloss.Width(opts.ExtraContent)
- if extraContentWidth > 0 {
- extraContentWidth += 1
- }
- description = ansi.Truncate(description, width-lipgloss.Width(icon)-lipgloss.Width(title)-2-extraContentWidth, "…")
- }
- description = t.S().Base.Foreground(descriptionColor).Render(description)
- content := []string{}
- if icon != "" {
- content = append(content, icon)
- }
- content = append(content, title, description)
- if opts.ExtraContent != "" {
- content = append(content, opts.ExtraContent)
- }
- return strings.Join(content, " ")
- }
- type ButtonOpts struct {
- Text string
- UnderlineIndex int // Index of character to underline (0-based)
- Selected bool // Whether this button is selected
- }
- // SelectableButton creates a button with an underlined character and selection state
- func SelectableButton(opts ButtonOpts) string {
- t := styles.CurrentTheme()
- // Base style for the button
- buttonStyle := t.S().Text
- // Apply selection styling
- if opts.Selected {
- buttonStyle = buttonStyle.Foreground(t.White).Background(t.Secondary)
- } else {
- buttonStyle = buttonStyle.Background(t.BgSubtle)
- }
- // Create the button text with underlined character
- text := opts.Text
- if opts.UnderlineIndex >= 0 && opts.UnderlineIndex < len(text) {
- before := text[:opts.UnderlineIndex]
- underlined := text[opts.UnderlineIndex : opts.UnderlineIndex+1]
- after := text[opts.UnderlineIndex+1:]
- message := buttonStyle.Render(before) +
- buttonStyle.Underline(true).Render(underlined) +
- buttonStyle.Render(after)
- return buttonStyle.Padding(0, 2).Render(message)
- }
- // Fallback if no underline index specified
- return buttonStyle.Padding(0, 2).Render(text)
- }
- // SelectableButtons creates a horizontal row of selectable buttons
- func SelectableButtons(buttons []ButtonOpts, spacing string) string {
- if spacing == "" {
- spacing = " "
- }
- var parts []string
- for i, button := range buttons {
- parts = append(parts, SelectableButton(button))
- if i < len(buttons)-1 {
- parts = append(parts, spacing)
- }
- }
- return lipgloss.JoinHorizontal(lipgloss.Left, parts...)
- }
- // SelectableButtonsVertical creates a vertical row of selectable buttons
- func SelectableButtonsVertical(buttons []ButtonOpts, spacing int) string {
- var parts []string
- for i, button := range buttons {
- parts = append(parts, SelectableButton(button))
- if i < len(buttons)-1 {
- for range spacing {
- parts = append(parts, "")
- }
- }
- }
- return lipgloss.JoinVertical(lipgloss.Center, parts...)
- }
- func DiffFormatter() *diffview.DiffView {
- t := styles.CurrentTheme()
- formatDiff := diffview.New()
- style := chroma.MustNewStyle("crush", styles.GetChromaTheme())
- diff := formatDiff.ChromaStyle(style).Style(t.S().Diff).TabWidth(4)
- return diff
- }
|