Detailed changes
@@ -4,6 +4,8 @@
- **Build**: `go build .` or `go run .`
- **Test**: `task test` or `go test ./...` (run single test: `go test ./internal/llm/prompt -run TestGetContextFromPaths`)
+- **Update Golden Files**: `go test ./... -update` (regenerates .golden files when test output changes)
+ - Update specific package: `go test ./internal/tui/components/core -update` (in this case, we're updating "core")
- **Lint**: `task lint-fix`
- **Format**: `task fmt` (gofumpt -w .)
- **Dev**: `task dev` (runs with profiling enabled)
@@ -281,7 +281,6 @@ func (m *messageCmp) renderThinkingContent() string {
opts := core.StatusOpts{
Title: "Thought for",
Description: duration.String(),
- NoIcon: true,
}
return t.S().Base.PaddingLeft(1).Render(core.Status(opts, m.textWidth()-1))
} else if finishReason != nil && finishReason.Reason == message.FinishReasonCanceled {
@@ -82,41 +82,30 @@ func Title(title string, width int) string {
}
type StatusOpts struct {
- Icon string
- IconColor color.Color
- NoIcon bool // If true, no icon will be displayed
+ 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
+ ExtraContent string // additional content to append after the description
}
-func Status(ops StatusOpts, width int) string {
+func Status(opts StatusOpts, width int) string {
t := styles.CurrentTheme()
- icon := "β"
- iconColor := t.Success
- if ops.Icon != "" {
- icon = ops.Icon
- } else if ops.NoIcon {
- icon = ""
- }
- if ops.IconColor != nil {
- iconColor = ops.IconColor
- }
- title := ops.Title
+ icon := opts.Icon
+ title := opts.Title
titleColor := t.FgMuted
- if ops.TitleColor != nil {
- titleColor = ops.TitleColor
+ if opts.TitleColor != nil {
+ titleColor = opts.TitleColor
}
- description := ops.Description
+ description := opts.Description
descriptionColor := t.FgSubtle
- if ops.DescriptionColor != nil {
- descriptionColor = ops.DescriptionColor
+ if opts.DescriptionColor != nil {
+ descriptionColor = opts.DescriptionColor
}
title = t.S().Base.Foreground(titleColor).Render(title)
if description != "" {
- extraContentWidth := lipgloss.Width(ops.ExtraContent)
+ extraContentWidth := lipgloss.Width(opts.ExtraContent)
if extraContentWidth > 0 {
extraContentWidth += 1
}
@@ -126,11 +115,11 @@ func Status(ops StatusOpts, width int) string {
content := []string{}
if icon != "" {
- content = append(content, t.S().Base.Foreground(iconColor).Render(icon))
+ content = append(content, icon)
}
content = append(content, title, description)
- if ops.ExtraContent != "" {
- content = append(content, ops.ExtraContent)
+ if opts.ExtraContent != "" {
+ content = append(content, opts.ExtraContent)
}
return strings.Join(content, " ")
@@ -37,7 +37,6 @@ func TestStatus(t *testing.T) {
{
name: "NoIcon",
opts: core.StatusOpts{
- NoIcon: true,
Title: "Info",
Description: "This status has no icon",
},
@@ -47,7 +46,6 @@ func TestStatus(t *testing.T) {
name: "WithColors",
opts: core.StatusOpts{
Icon: "β ",
- IconColor: color.RGBA{255, 165, 0, 255}, // Orange
Title: "Warning",
TitleColor: color.RGBA{255, 255, 0, 255}, // Yellow
Description: "This is a warning message",
@@ -102,7 +100,6 @@ func TestStatus(t *testing.T) {
name: "AllFieldsWithExtraContent",
opts: core.StatusOpts{
Icon: "π",
- IconColor: color.RGBA{0, 255, 0, 255}, // Green
Title: "Deployment",
TitleColor: color.RGBA{0, 0, 255, 255}, // Blue
Description: "Deploying to production environment",
@@ -1 +1 @@
-[38;2;0;255;0mπ[m [38;2;0;0;255mDeployment[m [38;2;128;128;128mDeploying to production environment[m v1.2.3
+π [38;2;0;0;255mDeployment[m [38;2;128;128;128mDeploying to production environment[m v1.2.3
@@ -1 +1 @@
-[38;2;18;199;143mβ[m [38;2;133;131;146mStatus[m [38;2;96;95;107mEverything is working fine[m
+[38;2;133;131;146mStatus[m [38;2;96;95;107mEverything is working fine[m
@@ -1 +1 @@
-[38;2;18;199;143mβ[m [38;2;133;131;146mTitle Only[m [38;2;96;95;107m[m
+β [38;2;133;131;146mTitle Only[m [38;2;96;95;107m[m
@@ -1 +1 @@
-[38;2;18;199;143mβ[m [38;2;133;131;146mProcessing[m [38;2;96;95;107mThis is a very long description that should beβ¦[m
+[38;2;133;131;146mProcessing[m [38;2;96;95;107mThis is a very long description that should be β¦[m
@@ -1 +1 @@
-[38;2;18;199;143mβ[m [38;2;133;131;146mStatus[m [38;2;96;95;107mShort message[m
+β [38;2;133;131;146mStatus[m [38;2;96;95;107mShort message[m
@@ -1 +1 @@
-[38;2;18;199;143mβ[m [38;2;133;131;146mTest[m [38;2;96;95;107mThis will beβ¦[m
+β [38;2;133;131;146mTest[m [38;2;96;95;107mThis will beβ¦[m
@@ -1 +1 @@
-[38;2;255;165;0mβ [m [38;2;255;255;0mWarning[m [38;2;255;0;0mThis is a warning message[m
+β [38;2;255;255;0mWarning[m [38;2;255;0;0mThis is a warning message[m
@@ -1 +1 @@
-[38;2;18;199;143mβ[m [38;2;133;131;146mSuccess[m [38;2;96;95;107mOperation completed successfully[m
+β [38;2;133;131;146mSuccess[m [38;2;96;95;107mOperation completed successfully[m
@@ -1 +1 @@
-[38;2;18;199;143mβ[m [38;2;133;131;146mBuild[m [38;2;96;95;107mBuilding project[m [2/5]
+[38;2;133;131;146mBuild[m [38;2;96;95;107mBuilding project[m [2/5]
@@ -1 +1 @@
-[38;2;18;199;143mβ[m [38;2;133;131;146mVery Long Title[m [38;2;96;95;107m[m [extra]
+β [38;2;133;131;146mVery Long Title[m [38;2;96;95;107m[m [extra]
@@ -1 +1 @@
-[38;2;18;199;143mβ[m [38;2;133;131;146mVery Long Title[m [38;2;96;95;107mThiβ¦[m [extra]
+β [38;2;133;131;146mVery Long Title[m [38;2;96;95;107mThiβ¦[m [extra]
@@ -1 +1 @@
-[38;2;18;199;143mβ[m [38;2;133;131;146mVery Long Title[m [38;2;96;95;107mThis is an exβ¦[m [extra]
+β [38;2;133;131;146mVery Long Title[m [38;2;96;95;107mThis is an exβ¦[m [extra]
@@ -1 +1 @@
-[38;2;18;199;143mβ[m [38;2;133;131;146mVery Long Title[m [38;2;96;95;107mThis is an extremely loβ¦[m [extra]
+β [38;2;133;131;146mVery Long Title[m [38;2;96;95;107mThis is an extremely loβ¦[m [extra]
@@ -1 +1 @@
-[38;2;18;199;143mβ[m [38;2;133;131;146mVery Long Title[m [38;2;96;95;107mThis is an extremely long descripβ¦[m [extra]
+β [38;2;133;131;146mVery Long Title[m [38;2;96;95;107mThis is an extremely long descripβ¦[m [extra]
@@ -98,8 +98,6 @@ func RenderFileList(fileSlice []SessionFile, opts RenderOptions) []string {
fileList = append(fileList,
core.Status(
core.StatusOpts{
- IconColor: t.FgMuted,
- NoIcon: true,
Title: filePath,
ExtraContent: extraContent,
},
@@ -57,22 +57,21 @@ func RenderLSPList(lspClients map[string]*lsp.Client, opts RenderOptions) []stri
}
// Determine icon color and description based on state
- iconColor := t.FgMuted
+ icon := t.ItemOfflineIcon
description := l.LSP.Command
if l.LSP.Disabled {
- iconColor = t.FgMuted
description = t.S().Subtle.Render("disabled")
} else if state, exists := lspStates[l.Name]; exists {
switch state.State {
case lsp.StateStarting:
- iconColor = t.Yellow
+ icon = t.ItemBusyIcon
description = t.S().Subtle.Render("starting...")
case lsp.StateReady:
- iconColor = t.Success
+ icon = t.ItemOnlineIcon
description = l.LSP.Command
case lsp.StateError:
- iconColor = t.Red
+ icon = t.ItemErrorIcon
if state.Error != nil {
description = t.S().Subtle.Render(fmt.Sprintf("error: %s", state.Error.Error()))
} else {
@@ -119,7 +118,7 @@ func RenderLSPList(lspClients map[string]*lsp.Client, opts RenderOptions) []stri
lspList = append(lspList,
core.Status(
core.StatusOpts{
- IconColor: iconColor,
+ Icon: icon.String(),
Title: l.Name,
Description: description,
ExtraContent: extraContent,
@@ -54,25 +54,24 @@ func RenderMCPList(opts RenderOptions) []string {
}
// Determine icon and color based on state
- iconColor := t.FgMuted
+ icon := t.ItemOfflineIcon
description := l.MCP.Command
extraContent := ""
if state, exists := mcpStates[l.Name]; exists {
switch state.State {
case agent.MCPStateDisabled:
- iconColor = t.FgMuted
description = t.S().Subtle.Render("disabled")
case agent.MCPStateStarting:
- iconColor = t.Yellow
+ icon = t.ItemBusyIcon
description = t.S().Subtle.Render("starting...")
case agent.MCPStateConnected:
- iconColor = t.Success
+ icon = t.ItemOnlineIcon
if state.ToolCount > 0 {
extraContent = t.S().Subtle.Render(fmt.Sprintf("(%d tools)", state.ToolCount))
}
case agent.MCPStateError:
- iconColor = t.Red
+ icon = t.ItemErrorIcon
if state.Error != nil {
description = t.S().Subtle.Render(fmt.Sprintf("error: %s", state.Error.Error()))
} else {
@@ -80,14 +79,13 @@ func RenderMCPList(opts RenderOptions) []string {
}
}
} else if l.MCP.Disabled {
- iconColor = t.FgMuted
description = t.S().Subtle.Render("disabled")
}
mcpList = append(mcpList,
core.Status(
core.StatusOpts{
- IconColor: iconColor,
+ Icon: icon.String(),
Title: l.Name,
Description: description,
ExtraContent: extraContent,
@@ -1,11 +1,12 @@
package styles
import (
+ "github.com/charmbracelet/lipgloss/v2"
"github.com/charmbracelet/x/exp/charmtone"
)
func NewCrushTheme() *Theme {
- return &Theme{
+ t := &Theme{
Name: "crush",
IsDark: true,
@@ -54,4 +55,12 @@ func NewCrushTheme() *Theme {
RedLight: charmtone.Salmon,
Cherry: charmtone.Cherry,
}
+
+ // LSP and MCP status.
+ t.ItemOfflineIcon = lipgloss.NewStyle().Foreground(charmtone.Squid).SetString("β")
+ t.ItemBusyIcon = t.ItemOfflineIcon.Foreground(charmtone.Citron)
+ t.ItemErrorIcon = t.ItemOfflineIcon.Foreground(charmtone.Coral)
+ t.ItemOnlineIcon = t.ItemOfflineIcon.Foreground(charmtone.Guac)
+
+ return t
}
@@ -74,6 +74,12 @@ type Theme struct {
RedLight color.Color
Cherry color.Color
+ // LSP and MCP status indicators.
+ ItemOfflineIcon lipgloss.Style
+ ItemBusyIcon lipgloss.Style
+ ItemErrorIcon lipgloss.Style
+ ItemOnlineIcon lipgloss.Style
+
styles *Styles
}