on main help sign in
chore: change LSP/MCP loading icon color + refactor icons + theme prep
19d3cd0Christian Rocha
internal/tui/components/chat/messages/messages.go−1
@@ -278,7 +278,6 @@ func (m *messageCmp) renderThinkingContent() string {
278 opts := core.StatusOpts{
278 opts := core.StatusOpts{
279 Title: "Thought for",
279 Title: "Thought for",
280 Description: duration.String(),
280 Description: duration.String(),
281− NoIcon: true,
282 }
281 }
283 return t.S().Base.PaddingLeft(1).Render(core.Status(opts, m.textWidth()-1))
282 return t.S().Base.PaddingLeft(1).Render(core.Status(opts, m.textWidth()-1))
284 } else if finishReason != nil && finishReason.Reason == message.FinishReasonCanceled {
283 } 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 {
82}
82}
83
83
84type StatusOpts struct {
84type StatusOpts struct {
85− Icon string
85+ Icon string // if empty no icon will be shown
86− IconColor color.Color
87− NoIcon bool // If true, no icon will be displayed
88 Title string
86 Title string
89 TitleColor color.Color
87 TitleColor color.Color
90 Description string
88 Description string
91 DescriptionColor color.Color
89 DescriptionColor color.Color
92− ExtraContent string // Additional content to append after the description
90+ ExtraContent string // additional content to append after the description
93}
91}
94
92
95−func Status(ops StatusOpts, width int) string {
93+func Status(opts StatusOpts, width int) string {
96 t := styles.CurrentTheme()
94 t := styles.CurrentTheme()
97− icon := "●"
95+ icon := opts.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
96+ title := opts.Title
108 titleColor := t.FgMuted
97 titleColor := t.FgMuted
109− if ops.TitleColor != nil {
98+ if opts.TitleColor != nil {
110− titleColor = ops.TitleColor
99+ titleColor = opts.TitleColor
111 }
100 }
112− description := ops.Description
101+ description := opts.Description
113 descriptionColor := t.FgSubtle
102 descriptionColor := t.FgSubtle
114− if ops.DescriptionColor != nil {
103+ if opts.DescriptionColor != nil {
115− descriptionColor = ops.DescriptionColor
104+ descriptionColor = opts.DescriptionColor
116 }
105 }
117 title = t.S().Base.Foreground(titleColor).Render(title)
106 title = t.S().Base.Foreground(titleColor).Render(title)
118 if description != "" {
107 if description != "" {
119− extraContentWidth := lipgloss.Width(ops.ExtraContent)
108+ extraContentWidth := lipgloss.Width(opts.ExtraContent)
120 if extraContentWidth > 0 {
109 if extraContentWidth > 0 {
121 extraContentWidth += 1
110 extraContentWidth += 1
122 }
111 }
@@ -126,11 +115,11 @@ func Status(ops StatusOpts, width int) string {
126
115
127 content := []string{}
116 content := []string{}
128 if icon != "" {
117 if icon != "" {
129− content = append(content, t.S().Base.Foreground(iconColor).Render(icon))
118+ content = append(content, icon)
130 }
119 }
131 content = append(content, title, description)
120 content = append(content, title, description)
132− if ops.ExtraContent != "" {
121+ if opts.ExtraContent != "" {
133− content = append(content, ops.ExtraContent)
122+ content = append(content, opts.ExtraContent)
134 }
123 }
135
124
136 return strings.Join(content, " ")
125 return strings.Join(content, " ")
internal/tui/components/files/files.go−2
@@ -98,8 +98,6 @@ func RenderFileList(fileSlice []SessionFile, opts RenderOptions) []string {
98 fileList = append(fileList,
98 fileList = append(fileList,
99 core.Status(
99 core.Status(
100 core.StatusOpts{
100 core.StatusOpts{
101− IconColor: t.FgMuted,
102− NoIcon: true,
103 Title: filePath,
101 Title: filePath,
104 ExtraContent: extraContent,
102 ExtraContent: extraContent,
105 },
103 },
internal/tui/components/lsp/lsp.go+6−6
@@ -57,22 +57,22 @@ func RenderLSPList(lspClients map[string]*lsp.Client, opts RenderOptions) []stri
57 }
57 }
58
58
59 // Determine icon color and description based on state
59 // Determine icon color and description based on state
60− iconColor := t.FgMuted
60+ // iconColor := t.FgMuted
61+ icon := t.ItemOfflineIcon
61 description := l.LSP.Command
62 description := l.LSP.Command
62
63
63 if l.LSP.Disabled {
64 if l.LSP.Disabled {
64− iconColor = t.FgMuted
65 description = t.S().Subtle.Render("disabled")
65 description = t.S().Subtle.Render("disabled")
66 } else if state, exists := lspStates[l.Name]; exists {
66 } else if state, exists := lspStates[l.Name]; exists {
67 switch state.State {
67 switch state.State {
68 case lsp.StateStarting:
68 case lsp.StateStarting:
69− iconColor = t.Yellow
69+ icon = t.ItemBusyIcon
70 description = t.S().Subtle.Render("starting...")
70 description = t.S().Subtle.Render("starting...")
71 case lsp.StateReady:
71 case lsp.StateReady:
72− iconColor = t.Success
72+ icon = t.ItemOnlineIcon
73 description = l.LSP.Command
73 description = l.LSP.Command
74 case lsp.StateError:
74 case lsp.StateError:
75− iconColor = t.Red
75+ icon = t.ItemErrorIcon
76 if state.Error != nil {
76 if state.Error != nil {
77 description = t.S().Subtle.Render(fmt.Sprintf("error: %s", state.Error.Error()))
77 description = t.S().Subtle.Render(fmt.Sprintf("error: %s", state.Error.Error()))
78 } else {
78 } else {
@@ -119,7 +119,7 @@ func RenderLSPList(lspClients map[string]*lsp.Client, opts RenderOptions) []stri
119 lspList = append(lspList,
119 lspList = append(lspList,
120 core.Status(
120 core.Status(
121 core.StatusOpts{
121 core.StatusOpts{
122− IconColor: iconColor,
122+ Icon: icon.String(),
123 Title: l.Name,
123 Title: l.Name,
124 Description: description,
124 Description: description,
125 ExtraContent: extraContent,
125 ExtraContent: extraContent,
internal/tui/components/mcp/mcp.go+5−7
@@ -54,25 +54,24 @@ func RenderMCPList(opts RenderOptions) []string {
54 }
54 }
55
55
56 // Determine icon and color based on state
56 // Determine icon and color based on state
57− iconColor := t.FgMuted
57+ icon := t.ItemOfflineIcon
58 description := l.MCP.Command
58 description := l.MCP.Command
59 extraContent := ""
59 extraContent := ""
60
60
61 if state, exists := mcpStates[l.Name]; exists {
61 if state, exists := mcpStates[l.Name]; exists {
62 switch state.State {
62 switch state.State {
63 case agent.MCPStateDisabled:
63 case agent.MCPStateDisabled:
64− iconColor = t.FgMuted
65 description = t.S().Subtle.Render("disabled")
64 description = t.S().Subtle.Render("disabled")
66 case agent.MCPStateStarting:
65 case agent.MCPStateStarting:
67− iconColor = t.Yellow
66+ icon = t.ItemBusyIcon
68 description = t.S().Subtle.Render("starting...")
67 description = t.S().Subtle.Render("starting...")
69 case agent.MCPStateConnected:
68 case agent.MCPStateConnected:
70− iconColor = t.Success
69+ icon = t.ItemOnlineIcon
71 if state.ToolCount > 0 {
70 if state.ToolCount > 0 {
72 extraContent = t.S().Subtle.Render(fmt.Sprintf("(%d tools)", state.ToolCount))
71 extraContent = t.S().Subtle.Render(fmt.Sprintf("(%d tools)", state.ToolCount))
73 }
72 }
74 case agent.MCPStateError:
73 case agent.MCPStateError:
75− iconColor = t.Red
74+ icon = t.ItemErrorIcon
76 if state.Error != nil {
75 if state.Error != nil {
77 description = t.S().Subtle.Render(fmt.Sprintf("error: %s", state.Error.Error()))
76 description = t.S().Subtle.Render(fmt.Sprintf("error: %s", state.Error.Error()))
78 } else {
77 } else {
@@ -80,14 +79,13 @@ func RenderMCPList(opts RenderOptions) []string {
80 }
79 }
81 }
80 }
82 } else if l.MCP.Disabled {
81 } else if l.MCP.Disabled {
83− iconColor = t.FgMuted
84 description = t.S().Subtle.Render("disabled")
82 description = t.S().Subtle.Render("disabled")
85 }
83 }
86
84
87 mcpList = append(mcpList,
85 mcpList = append(mcpList,
88 core.Status(
86 core.Status(
89 core.StatusOpts{
87 core.StatusOpts{
90− IconColor: iconColor,
88+ Icon: icon.String(),
91 Title: l.Name,
89 Title: l.Name,
92 Description: description,
90 Description: description,
93 ExtraContent: extraContent,
91 ExtraContent: extraContent,
internal/tui/styles/crush.go+10−1
@@ -1,11 +1,12 @@
1package styles
1package styles
2
2
3import (
3import (
4+ "github.com/charmbracelet/lipgloss/v2"
4 "github.com/charmbracelet/x/exp/charmtone"
5 "github.com/charmbracelet/x/exp/charmtone"
5)
6)
6
7
7func NewCrushTheme() *Theme {
8func NewCrushTheme() *Theme {
8− return &Theme{
9+ t := &Theme{
9 Name: "crush",
10 Name: "crush",
10 IsDark: true,
11 IsDark: true,
11
12
@@ -54,4 +55,12 @@ func NewCrushTheme() *Theme {
54 RedLight: charmtone.Salmon,
55 RedLight: charmtone.Salmon,
55 Cherry: charmtone.Cherry,
56 Cherry: charmtone.Cherry,
56 }
57 }
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
57}
66}
internal/tui/styles/theme.go+6
@@ -74,6 +74,12 @@ type Theme struct {
74 RedLight color.Color
74 RedLight color.Color
75 Cherry color.Color
75 Cherry color.Color
76
76
77+ // LSP and MCP status indicators.
78+ ItemOfflineIcon lipgloss.Style
79+ ItemBusyIcon lipgloss.Style
80+ ItemErrorIcon lipgloss.Style
81+ ItemOnlineIcon lipgloss.Style
82+
77 styles *Styles
83 styles *Styles
78}
84}
79
85