7 "charm.land/bubbles/v2/help"
8 "charm.land/bubbles/v2/key"
9 "charm.land/lipgloss/v2"
10 "github.com/alecthomas/chroma/v2"
11 "github.com/charmbracelet/crush/internal/tui/exp/diffview"
12 "github.com/charmbracelet/crush/internal/tui/styles"
13 "github.com/charmbracelet/x/ansi"
16type KeyMapHelp interface {
20type simpleHelp struct {
21 shortList []key.Binding
22 fullList [][]key.Binding
25func NewSimpleHelp(shortList []key.Binding, fullList [][]key.Binding) help.KeyMap {
32// FullHelp implements help.KeyMap.
33func (s *simpleHelp) FullHelp() [][]key.Binding {
37// ShortHelp implements help.KeyMap.
38func (s *simpleHelp) ShortHelp() []key.Binding {
42func Section(text string, width int) string {
43 t := styles.CurrentTheme()
45 length := lipgloss.Width(text) + 1
46 remainingWidth := width - length
47 lineStyle := t.S().Base.Foreground(t.Border)
48 if remainingWidth > 0 {
49 text = text + " " + lineStyle.Render(strings.Repeat(char, remainingWidth))
54func SectionWithInfo(text string, width int, info string) string {
55 t := styles.CurrentTheme()
57 length := lipgloss.Width(text) + 1
58 remainingWidth := width - length
61 remainingWidth -= lipgloss.Width(info) + 1 // 1 for the space before info
63 lineStyle := t.S().Base.Foreground(t.Border)
64 if remainingWidth > 0 {
65 text = text + " " + lineStyle.Render(strings.Repeat(char, remainingWidth)) + " " + info
70func Title(title string, width int) string {
71 t := styles.CurrentTheme()
73 length := lipgloss.Width(title) + 1
74 remainingWidth := width - length
75 titleStyle := t.S().Base.Foreground(t.Primary)
76 if remainingWidth > 0 {
77 lines := strings.Repeat(char, remainingWidth)
78 lines = styles.ApplyForegroundGrad(lines, t.Primary, t.Secondary)
79 title = titleStyle.Render(title) + " " + lines
84type StatusOpts struct {
85 Icon string // if empty no icon will be shown
87 TitleColor color.Color
89 DescriptionColor color.Color
90 ExtraContent string // additional content to append after the description
93func Status(opts StatusOpts, width int) string {
94 t := styles.CurrentTheme()
97 titleColor := t.FgMuted
98 if opts.TitleColor != nil {
99 titleColor = opts.TitleColor
101 description := opts.Description
102 descriptionColor := t.FgSubtle
103 if opts.DescriptionColor != nil {
104 descriptionColor = opts.DescriptionColor
106 title = t.S().Base.Foreground(titleColor).Render(title)
107 if description != "" {
108 extraContentWidth := lipgloss.Width(opts.ExtraContent)
109 if extraContentWidth > 0 {
110 extraContentWidth += 1
112 description = ansi.Truncate(description, width-lipgloss.Width(icon)-lipgloss.Width(title)-2-extraContentWidth, "…")
113 description = t.S().Base.Foreground(descriptionColor).Render(description)
116 content := []string{}
118 content = append(content, icon)
120 content = append(content, title)
121 if description != "" {
122 content = append(content, description)
124 if opts.ExtraContent != "" {
125 content = append(content, opts.ExtraContent)
128 return strings.Join(content, " ")
131type ButtonOpts struct {
133 UnderlineIndex int // Index of character to underline (0-based)
134 Selected bool // Whether this button is selected
137// SelectableButton creates a button with an underlined character and selection state
138func SelectableButton(opts ButtonOpts) string {
139 t := styles.CurrentTheme()
141 // Base style for the button
142 buttonStyle := t.S().Text
144 // Apply selection styling
146 buttonStyle = buttonStyle.Foreground(t.White).Background(t.Secondary)
148 buttonStyle = buttonStyle.Background(t.BgSubtle)
151 // Create the button text with underlined character
153 if opts.UnderlineIndex >= 0 && opts.UnderlineIndex < len(text) {
154 before := text[:opts.UnderlineIndex]
155 underlined := text[opts.UnderlineIndex : opts.UnderlineIndex+1]
156 after := text[opts.UnderlineIndex+1:]
158 message := buttonStyle.Render(before) +
159 buttonStyle.Underline(true).Render(underlined) +
160 buttonStyle.Render(after)
162 return buttonStyle.Padding(0, 2).Render(message)
165 // Fallback if no underline index specified
166 return buttonStyle.Padding(0, 2).Render(text)
169// SelectableButtons creates a horizontal row of selectable buttons
170func SelectableButtons(buttons []ButtonOpts, spacing string) string {
176 for i, button := range buttons {
177 parts = append(parts, SelectableButton(button))
178 if i < len(buttons)-1 {
179 parts = append(parts, spacing)
183 return lipgloss.JoinHorizontal(lipgloss.Left, parts...)
186// SelectableButtonsVertical creates a vertical row of selectable buttons
187func SelectableButtonsVertical(buttons []ButtonOpts, spacing int) string {
189 for i, button := range buttons {
190 parts = append(parts, SelectableButton(button))
191 if i < len(buttons)-1 {
193 parts = append(parts, "")
198 return lipgloss.JoinVertical(lipgloss.Center, parts...)
201func DiffFormatter() *diffview.DiffView {
202 t := styles.CurrentTheme()
203 formatDiff := diffview.New()
204 style := chroma.MustNewStyle("crush", styles.GetChromaTheme())
205 diff := formatDiff.ChromaStyle(style).Style(t.S().Diff).TabWidth(4)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list