on main help sign in
chore: change LSP/MCP loading icon color + refactor icons + theme prep
commit19d3cd06eed1a9cb594a0e31bd18d113d6de5729 authorChristian Rocha <christian@rocha.is> dateMon 4 Aug 2025, 20:58 (1 year ago) parentdcbc273
7 files changed+41−42
internal/tui/components/chat/messages/messages.go−1 internal/tui/components/core/core.go+14−25 internal/tui/components/files/files.go−2 internal/tui/components/lsp/lsp.go+6−6 internal/tui/components/mcp/mcp.go+5−7 internal/tui/styles/crush.go+10−1 internal/tui/styles/theme.go+6
internal/tui/components/chat/messages/messages.go−1
@@ -278,7 +278,6 @@ func (m *messageCmp) renderThinkingContent() string {
278278 opts := core.StatusOpts{
279279 Title: "Thought for",
280280 Description: duration.String(),
281− NoIcon: true,
282281 }
283282 return t.S().Base.PaddingLeft(1).Render(core.Status(opts, m.textWidth()-1))
284283 } else if finishReason != nil && finishReason.Reason == message.FinishReasonCanceled {
internal/tui/components/core/core.go+14−25
@@ -82,41 +82,30 @@ func Title(title string, width int) string {
8282}
8383
8484type StatusOpts struct {
85− Icon string
86− IconColor color.Color
87− NoIcon bool // If true, no icon will be displayed
85+ Icon string // if empty no icon will be shown
8886 Title string
8987 TitleColor color.Color
9088 Description string
9189 DescriptionColor color.Color
92− ExtraContent string // Additional content to append after the description
90+ ExtraContent string // additional content to append after the description
9391}
9492
95−func Status(ops StatusOpts, width int) string {
93+func Status(opts StatusOpts, width int) string {
9694 t := styles.CurrentTheme()
97− icon := "●"
98− iconColor := t.Success
99− if ops.Icon != "" {
100− icon = ops.Icon
101− } else if ops.NoIcon {
102− icon = ""
103− }
104− if ops.IconColor != nil {
105− iconColor = ops.IconColor
106− }
107− title := ops.Title
95+ icon := opts.Icon
96+ title := opts.Title
10897 titleColor := t.FgMuted
109− if ops.TitleColor != nil {
110− titleColor = ops.TitleColor
98+ if opts.TitleColor != nil {
99+ titleColor = opts.TitleColor
111100 }
112− description := ops.Description
101+ description := opts.Description
113102 descriptionColor := t.FgSubtle
114− if ops.DescriptionColor != nil {
115− descriptionColor = ops.DescriptionColor
103+ if opts.DescriptionColor != nil {
104+ descriptionColor = opts.DescriptionColor
116105 }
117106 title = t.S().Base.Foreground(titleColor).Render(title)
118107 if description != "" {
119− extraContentWidth := lipgloss.Width(ops.ExtraContent)
108+ extraContentWidth := lipgloss.Width(opts.ExtraContent)
120109 if extraContentWidth > 0 {
121110 extraContentWidth += 1
122111 }
@@ -126,11 +115,11 @@ func Status(ops StatusOpts, width int) string {
126115
127116 content := []string{}
128117 if icon != "" {
129− content = append(content, t.S().Base.Foreground(iconColor).Render(icon))
118+ content = append(content, icon)
130119 }
131120 content = append(content, title, description)
132− if ops.ExtraContent != "" {
133− content = append(content, ops.ExtraContent)
121+ if opts.ExtraContent != "" {
122+ content = append(content, opts.ExtraContent)
134123 }
135124
136125 return strings.Join(content, " ")
internal/tui/components/files/files.go−2
@@ -98,8 +98,6 @@ func RenderFileList(fileSlice []SessionFile, opts RenderOptions) []string {
9898 fileList = append(fileList,
9999 core.Status(
100100 core.StatusOpts{
101− IconColor: t.FgMuted,
102− NoIcon: true,
103101 Title: filePath,
104102 ExtraContent: extraContent,
105103 },
internal/tui/components/lsp/lsp.go+6−6
@@ -57,22 +57,22 @@ func RenderLSPList(lspClients map[string]*lsp.Client, opts RenderOptions) []stri
5757 }
5858
5959 // Determine icon color and description based on state
60− iconColor := t.FgMuted
60+ // iconColor := t.FgMuted
61+ icon := t.ItemOfflineIcon
6162 description := l.LSP.Command
6263
6364 if l.LSP.Disabled {
64− iconColor = t.FgMuted
6565 description = t.S().Subtle.Render("disabled")
6666 } else if state, exists := lspStates[l.Name]; exists {
6767 switch state.State {
6868 case lsp.StateStarting:
69− iconColor = t.Yellow
69+ icon = t.ItemBusyIcon
7070 description = t.S().Subtle.Render("starting...")
7171 case lsp.StateReady:
72− iconColor = t.Success
72+ icon = t.ItemOnlineIcon
7373 description = l.LSP.Command
7474 case lsp.StateError:
75− iconColor = t.Red
75+ icon = t.ItemErrorIcon
7676 if state.Error != nil {
7777 description = t.S().Subtle.Render(fmt.Sprintf("error: %s", state.Error.Error()))
7878 } else {
@@ -119,7 +119,7 @@ func RenderLSPList(lspClients map[string]*lsp.Client, opts RenderOptions) []stri
119119 lspList = append(lspList,
120120 core.Status(
121121 core.StatusOpts{
122− IconColor: iconColor,
122+ Icon: icon.String(),
123123 Title: l.Name,
124124 Description: description,
125125 ExtraContent: extraContent,
internal/tui/components/mcp/mcp.go+5−7
@@ -54,25 +54,24 @@ func RenderMCPList(opts RenderOptions) []string {
5454 }
5555
5656 // Determine icon and color based on state
57− iconColor := t.FgMuted
57+ icon := t.ItemOfflineIcon
5858 description := l.MCP.Command
5959 extraContent := ""
6060
6161 if state, exists := mcpStates[l.Name]; exists {
6262 switch state.State {
6363 case agent.MCPStateDisabled:
64− iconColor = t.FgMuted
6564 description = t.S().Subtle.Render("disabled")
6665 case agent.MCPStateStarting:
67− iconColor = t.Yellow
66+ icon = t.ItemBusyIcon
6867 description = t.S().Subtle.Render("starting...")
6968 case agent.MCPStateConnected:
70− iconColor = t.Success
69+ icon = t.ItemOnlineIcon
7170 if state.ToolCount > 0 {
7271 extraContent = t.S().Subtle.Render(fmt.Sprintf("(%d tools)", state.ToolCount))
7372 }
7473 case agent.MCPStateError:
75− iconColor = t.Red
74+ icon = t.ItemErrorIcon
7675 if state.Error != nil {
7776 description = t.S().Subtle.Render(fmt.Sprintf("error: %s", state.Error.Error()))
7877 } else {
@@ -80,14 +79,13 @@ func RenderMCPList(opts RenderOptions) []string {
8079 }
8180 }
8281 } else if l.MCP.Disabled {
83− iconColor = t.FgMuted
8482 description = t.S().Subtle.Render("disabled")
8583 }
8684
8785 mcpList = append(mcpList,
8886 core.Status(
8987 core.StatusOpts{
90− IconColor: iconColor,
88+ Icon: icon.String(),
9189 Title: l.Name,
9290 Description: description,
9391 ExtraContent: extraContent,
internal/tui/styles/crush.go+10−1
@@ -1,11 +1,12 @@
11package styles
22
33import (
4+ "github.com/charmbracelet/lipgloss/v2"
45 "github.com/charmbracelet/x/exp/charmtone"
56)
67
78func NewCrushTheme() *Theme {
8− return &Theme{
9+ t := &Theme{
910 Name: "crush",
1011 IsDark: true,
1112
@@ -54,4 +55,12 @@ func NewCrushTheme() *Theme {
5455 RedLight: charmtone.Salmon,
5556 Cherry: charmtone.Cherry,
5657 }
58+
59+ // LSP and MCP status.
60+ t.ItemOfflineIcon = lipgloss.NewStyle().Foreground(charmtone.Squid).SetString("●")
61+ t.ItemBusyIcon = t.ItemOfflineIcon.Foreground(charmtone.Citron)
62+ t.ItemErrorIcon = t.ItemOfflineIcon.Foreground(charmtone.Coral)
63+ t.ItemOnlineIcon = t.ItemOfflineIcon.Foreground(charmtone.Guac)
64+
65+ return t
5766}
internal/tui/styles/theme.go+6
@@ -74,6 +74,12 @@ type Theme struct {
7474 RedLight color.Color
7575 Cherry color.Color
7676
77+ // LSP and MCP status indicators.
78+ ItemOfflineIcon lipgloss.Style
79+ ItemBusyIcon lipgloss.Style
80+ ItemErrorIcon lipgloss.Style
81+ ItemOnlineIcon lipgloss.Style
82+
7783 styles *Styles
7884}
7985