diff --git a/internal/tui/components/chat/messages/messages.go b/internal/tui/components/chat/messages/messages.go index 17bb582dcadbea1f314b976bc31a31639f8d9609..907efab4eaf22aac6ce4f5ff2ec5afbb244aed2b 100644 --- a/internal/tui/components/chat/messages/messages.go +++ b/internal/tui/components/chat/messages/messages.go @@ -278,7 +278,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 { diff --git a/internal/tui/components/core/core.go b/internal/tui/components/core/core.go index 9f6657de33d5ed824b7d5bc7086342e8f3ec7d48..18de56b17f08e4513bde34fe9fef7aaf4e08c09f 100644 --- a/internal/tui/components/core/core.go +++ b/internal/tui/components/core/core.go @@ -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, " ") diff --git a/internal/tui/components/files/files.go b/internal/tui/components/files/files.go index 234a75fd4e06431018eab1fbf37e90e562da4083..8272bd53900acf4dd032f86b8f9d2a0bd3b52ccd 100644 --- a/internal/tui/components/files/files.go +++ b/internal/tui/components/files/files.go @@ -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, }, diff --git a/internal/tui/components/lsp/lsp.go b/internal/tui/components/lsp/lsp.go index 10d9f42198a6996e966d01305131e734fa54a614..893e6470d9f99992fe9d195e5500db8a3891c763 100644 --- a/internal/tui/components/lsp/lsp.go +++ b/internal/tui/components/lsp/lsp.go @@ -57,22 +57,22 @@ func RenderLSPList(lspClients map[string]*lsp.Client, opts RenderOptions) []stri } // Determine icon color and description based on state - iconColor := t.FgMuted + // 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 +119,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, diff --git a/internal/tui/components/mcp/mcp.go b/internal/tui/components/mcp/mcp.go index 93f2dcb230721ab95c3ea2f4937647ff7ccf5bda..2376011ae1f18f44962d59f142652a52bfc47c3d 100644 --- a/internal/tui/components/mcp/mcp.go +++ b/internal/tui/components/mcp/mcp.go @@ -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, diff --git a/internal/tui/styles/crush.go b/internal/tui/styles/crush.go index 2c54d5e41c91521b9418cdcdd4bcbc5dc7231eee..f27632784ad64ed3228ee548c7c8fe84b58bc9ec 100644 --- a/internal/tui/styles/crush.go +++ b/internal/tui/styles/crush.go @@ -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 } diff --git a/internal/tui/styles/theme.go b/internal/tui/styles/theme.go index 1d6967684c6ccb5c8f9db2dd23300600b2b5af15..e917cb2b6ffc1ff864012366e0711b66ccf1be83 100644 --- a/internal/tui/styles/theme.go +++ b/internal/tui/styles/theme.go @@ -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 }