From 755f6fae6f723333adfe47c01949409d243fdfdb Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Sun, 26 Apr 2026 10:46:55 -0400 Subject: [PATCH] chore(ui): theme prep --- internal/app/app.go | 2 +- internal/cmd/run.go | 2 +- internal/cmd/session.go | 4 +- internal/ui/chat/tool_result_content_test.go | 2 +- internal/ui/common/common.go | 2 +- internal/ui/logo/example/main.go | 2 +- internal/ui/model/skills_test.go | 4 +- internal/ui/styles/quickstyle.go | 968 +++++++++++++++++++ internal/ui/styles/styles.go | 943 +----------------- internal/ui/styles/themes.go | 41 + 10 files changed, 1027 insertions(+), 943 deletions(-) create mode 100644 internal/ui/styles/quickstyle.go create mode 100644 internal/ui/styles/themes.go diff --git a/internal/app/app.go b/internal/app/app.go index 80816269fb8bef5e90a4ab45b38a400e21aea6f7..fe31bda6f03d50cd53c3811568858bbfc9316974 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -233,7 +233,7 @@ func (app *App) RunNonInteractive(ctx context.Context, output io.Writer, prompt, progress = app.config.Config().Options.Progress == nil || *app.config.Config().Options.Progress if !hideSpinner && stderrTTY { - t := styles.DefaultStyles() + t := styles.CharmtonePantera() // Detect background color to set the appropriate color for the // spinner's 'Generating...' text. Without this, that text would be diff --git a/internal/cmd/run.go b/internal/cmd/run.go index ed3d808d410640a580f8021d29e6b3eb7fccaa37..4f73fe5644b9c7ba39ec8d4246c2dc72773469e6 100644 --- a/internal/cmd/run.go +++ b/internal/cmd/run.go @@ -189,7 +189,7 @@ func runNonInteractive( progress = ws.Config.Options.Progress == nil || *ws.Config.Options.Progress if !hideSpinner && stderrTTY { - t := styles.DefaultStyles() + t := styles.CharmtonePantera() hasDarkBG := true if stdinTTY && stdoutTTY { diff --git a/internal/cmd/session.go b/internal/cmd/session.go index d9042f4298b67250168042752871fc4899256aaa..d673ad07c3d11191506498ff4f97410eb1177596 100644 --- a/internal/cmd/session.go +++ b/internal/cmd/session.go @@ -438,7 +438,7 @@ func outputSessionJSON(w io.Writer, sess session.Session, msgs []*message.Messag } func outputSessionHuman(ctx context.Context, sess session.Session, msgs []*message.Message) error { - sty := styles.DefaultStyles() + styles := styles.CharmtonePantera() toolResults := chat.BuildToolResultMap(msgs) width := sessionOutputWidth @@ -479,7 +479,7 @@ func outputSessionHuman(ctx context.Context, sess session.Session, msgs []*messa first := true for _, msg := range msgs { - items := chat.ExtractMessageItems(&sty, msg, toolResults) + items := chat.ExtractMessageItems(&styles, msg, toolResults) for _, item := range items { if !first { fmt.Fprintln(&buf) diff --git a/internal/ui/chat/tool_result_content_test.go b/internal/ui/chat/tool_result_content_test.go index 5020d81bce6d514cd2dffcfb042ccae7711fcb50..7b7a8ac9643e4fc28ecb8344e97048255dd64123 100644 --- a/internal/ui/chat/tool_result_content_test.go +++ b/internal/ui/chat/tool_result_content_test.go @@ -56,7 +56,7 @@ func TestLooksLikeMarkdown(t *testing.T) { func TestRenderToolResultTextContent(t *testing.T) { t.Parallel() - sty := styles.DefaultStyles() + sty := styles.CharmtonePantera() styPtr := &sty widths := toolResultContentWidths{Body: 80, Diff: 82} diff --git a/internal/ui/common/common.go b/internal/ui/common/common.go index 8e00f0d0d2a74396df36e4b4d97762a9087087be..798ee2c1bf1d99aa15d4f343956bf16170c147f3 100644 --- a/internal/ui/common/common.go +++ b/internal/ui/common/common.go @@ -33,7 +33,7 @@ func (c *Common) Config() *config.Config { // DefaultCommon returns the default common UI configurations. func DefaultCommon(ws workspace.Workspace) *Common { - s := styles.DefaultStyles() + s := styles.CharmtonePantera() return &Common{ Workspace: ws, Styles: &s, diff --git a/internal/ui/logo/example/main.go b/internal/ui/logo/example/main.go index c250b325d6025fd8f284821dba3203902629816f..5a90ec109df047a8f25c383c90dfc0cd468c01d7 100644 --- a/internal/ui/logo/example/main.go +++ b/internal/ui/logo/example/main.go @@ -18,7 +18,7 @@ func main() { fmt.Fprintf(os.Stderr, "Could not get terminal size: %s", err) } - s := styles.DefaultStyles() + s := styles.CharmtonePantera() opts := logo.Opts{ FieldColor: s.Logo.FieldColor, TitleColorA: s.Logo.TitleColorA, diff --git a/internal/ui/model/skills_test.go b/internal/ui/model/skills_test.go index 74039d6bf51e997b3f2a574bc962e42a0c834913..74a9eb968e33a40dfd67dc2154873ab4f7e729a8 100644 --- a/internal/ui/model/skills_test.go +++ b/internal/ui/model/skills_test.go @@ -16,7 +16,7 @@ import ( func TestSkillStatusItemsIncludesBuiltinSkills(t *testing.T) { t.Parallel() - st := uistyles.DefaultStyles() + st := uistyles.CharmtonePantera() ui := &UI{ com: &common.Common{Styles: &st}, skillStates: []*skills.SkillState{ @@ -61,7 +61,7 @@ func TestSkillStatusItemsIncludesBuiltinSkills(t *testing.T) { func TestSkillStatusItemsExcludesDisabledSkills(t *testing.T) { t.Parallel() - st := uistyles.DefaultStyles() + st := uistyles.CharmtonePantera() ui := &UI{ com: &common.Common{ Styles: &st, diff --git a/internal/ui/styles/quickstyle.go b/internal/ui/styles/quickstyle.go new file mode 100644 index 0000000000000000000000000000000000000000..0adb16ed2f89e29f9da6556dbea9ee0577032f68 --- /dev/null +++ b/internal/ui/styles/quickstyle.go @@ -0,0 +1,968 @@ +package styles + +import ( + "image/color" + + "charm.land/bubbles/v2/filepicker" + "charm.land/bubbles/v2/help" + "charm.land/bubbles/v2/textarea" + "charm.land/bubbles/v2/textinput" + tea "charm.land/bubbletea/v2" + "charm.land/glamour/v2/ansi" + "charm.land/lipgloss/v2" + "github.com/charmbracelet/crush/internal/ui/diffview" + "github.com/charmbracelet/x/exp/charmtone" +) + +// quickStyleOpts is the palette of colors used by quickStyle to build a +// complete Styles value. Each field maps to a semantic role in the UI. +type quickStyleOpts struct { + // Brand. + primary color.Color + secondary color.Color + tertiary color.Color + + // Foregrounds. + fgBase color.Color + fgMuted color.Color + fgHalfMuted color.Color + fgSubtle color.Color + + // Contrast pairings: foregrounds designed to sit on top of a + // matching background role. + onPrimary color.Color // foreground on primary backgrounds. + onAccent color.Color // foreground on saturated status/accent backgrounds. + + // Backgrounds. + bgBase color.Color + bgBaseLighter color.Color + bgSubtle color.Color + bgOverlay color.Color + + // Borders. + border color.Color + borderFocus color.Color + + // Status. + danger color.Color + error color.Color + warning color.Color + warningStrong color.Color + busy color.Color + info color.Color + infoSubtle color.Color + infoMuted color.Color + success color.Color + successSubtle color.Color + successMuted color.Color +} + +// quickStyle builds a complete Styles value from a palette of semantic +// colors. Themes should populate quickStyleOpts and call this rather than +// re-implementing every style rule. +// +// The idea here is that you can do most of the work with quickStyle, then +// add overrides as needed. +func quickStyle(o quickStyleOpts) Styles { + var ( + primary = o.primary + secondary = o.secondary + tertiary = o.tertiary + + fgBase = o.fgBase + fgMuted = o.fgMuted + fgHalfMuted = o.fgHalfMuted + fgSubtle = o.fgSubtle + + onPrimary = o.onPrimary + onAccent = o.onAccent + + bgBase = o.bgBase + bgBaseLighter = o.bgBaseLighter + bgSubtle = o.bgSubtle + bgOverlay = o.bgOverlay + + border = o.border + borderFocus = o.borderFocus + + danger = o.danger + error = o.error + warning = o.warning + warningStrong = o.warningStrong + busy = o.busy + info = o.info + infoSubtle = o.infoSubtle + infoMuted = o.infoMuted + success = o.success + successSubtle = o.successSubtle + successMuted = o.successMuted + ) + + var ( + base = lipgloss.NewStyle().Foreground(fgBase) + muted = lipgloss.NewStyle().Foreground(fgMuted) + subtle = lipgloss.NewStyle().Foreground(fgSubtle) + s Styles + ) + + s.Background = bgBase + + // Populate color fields + s.WorkingGradFromColor = primary + s.WorkingGradToColor = secondary + s.WorkingLabelColor = fgBase + + s.TextInput = textinput.Styles{ + Focused: textinput.StyleState{ + Text: base, + Placeholder: base.Foreground(fgSubtle), + Prompt: base.Foreground(tertiary), + Suggestion: base.Foreground(fgSubtle), + }, + Blurred: textinput.StyleState{ + Text: base.Foreground(fgMuted), + Placeholder: base.Foreground(fgSubtle), + Prompt: base.Foreground(fgMuted), + Suggestion: base.Foreground(fgSubtle), + }, + Cursor: textinput.CursorStyle{ + Color: secondary, + Shape: tea.CursorBlock, + Blink: true, + }, + } + + s.Editor.Textarea = textarea.Styles{ + Focused: textarea.StyleState{ + Base: base, + Text: base, + LineNumber: base.Foreground(fgSubtle), + CursorLine: base, + CursorLineNumber: base.Foreground(fgSubtle), + Placeholder: base.Foreground(fgSubtle), + Prompt: base.Foreground(tertiary), + }, + Blurred: textarea.StyleState{ + Base: base, + Text: base.Foreground(fgMuted), + LineNumber: base.Foreground(fgMuted), + CursorLine: base, + CursorLineNumber: base.Foreground(fgMuted), + Placeholder: base.Foreground(fgSubtle), + Prompt: base.Foreground(fgMuted), + }, + Cursor: textarea.CursorStyle{ + Color: secondary, + Shape: tea.CursorBlock, + Blink: true, + }, + } + + s.Markdown = ansi.StyleConfig{ + Document: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + // BlockPrefix: "\n", + // BlockSuffix: "\n", + Color: hex(fgHalfMuted), + }, + // Margin: new(uint(defaultMargin)), + }, + BlockQuote: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{}, + Indent: new(uint(1)), + IndentToken: new("│ "), + }, + List: ansi.StyleList{ + LevelIndent: defaultListIndent, + }, + Heading: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + BlockSuffix: "\n", + Color: hex(info), + Bold: new(true), + }, + }, + H1: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: " ", + Suffix: " ", + Color: hex(warning), + BackgroundColor: hex(primary), + Bold: new(true), + }, + }, + H2: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "## ", + }, + }, + H3: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "### ", + }, + }, + H4: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "#### ", + }, + }, + H5: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "##### ", + }, + }, + H6: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "###### ", + Color: hex(successMuted), + Bold: new(false), + }, + }, + Strikethrough: ansi.StylePrimitive{ + CrossedOut: new(true), + }, + Emph: ansi.StylePrimitive{ + Italic: new(true), + }, + Strong: ansi.StylePrimitive{ + Bold: new(true), + }, + HorizontalRule: ansi.StylePrimitive{ + Color: hex(border), + Format: "\n--------\n", + }, + Item: ansi.StylePrimitive{ + BlockPrefix: "• ", + }, + Enumeration: ansi.StylePrimitive{ + BlockPrefix: ". ", + }, + Task: ansi.StyleTask{ + StylePrimitive: ansi.StylePrimitive{}, + Ticked: "[✓] ", + Unticked: "[ ] ", + }, + Link: ansi.StylePrimitive{ + Color: hex(charmtone.Zinc), + Underline: new(true), + }, + LinkText: ansi.StylePrimitive{ + Color: hex(successMuted), + Bold: new(true), + }, + Image: ansi.StylePrimitive{ + Color: hex(charmtone.Cheeky), + Underline: new(true), + }, + ImageText: ansi.StylePrimitive{ + Color: hex(fgMuted), + Format: "Image: {{.text}} →", + }, + Code: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: " ", + Suffix: " ", + Color: hex(danger), + BackgroundColor: hex(bgSubtle), + }, + }, + CodeBlock: ansi.StyleCodeBlock{ + StyleBlock: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: hex(bgSubtle), + }, + Margin: new(uint(defaultMargin)), + }, + Chroma: &ansi.Chroma{ + Text: ansi.StylePrimitive{ + Color: hex(fgHalfMuted), + }, + Error: ansi.StylePrimitive{ + Color: hex(onAccent), + BackgroundColor: hex(error), + }, + Comment: ansi.StylePrimitive{ + Color: hex(fgSubtle), + }, + CommentPreproc: ansi.StylePrimitive{ + Color: hex(charmtone.Bengal), + }, + Keyword: ansi.StylePrimitive{ + Color: hex(info), + }, + KeywordReserved: ansi.StylePrimitive{ + Color: hex(charmtone.Pony), + }, + KeywordNamespace: ansi.StylePrimitive{ + Color: hex(charmtone.Pony), + }, + KeywordType: ansi.StylePrimitive{ + Color: hex(charmtone.Guppy), + }, + Operator: ansi.StylePrimitive{ + Color: hex(charmtone.Salmon), + }, + Punctuation: ansi.StylePrimitive{ + Color: hex(warning), + }, + Name: ansi.StylePrimitive{ + Color: hex(fgHalfMuted), + }, + NameBuiltin: ansi.StylePrimitive{ + Color: hex(charmtone.Cheeky), + }, + NameTag: ansi.StylePrimitive{ + Color: hex(charmtone.Mauve), + }, + NameAttribute: ansi.StylePrimitive{ + Color: hex(charmtone.Hazy), + }, + NameClass: ansi.StylePrimitive{ + Color: hex(charmtone.Salt), + Underline: new(true), + Bold: new(true), + }, + NameDecorator: ansi.StylePrimitive{ + Color: hex(charmtone.Citron), + }, + NameFunction: ansi.StylePrimitive{ + Color: hex(successMuted), + }, + LiteralNumber: ansi.StylePrimitive{ + Color: hex(success), + }, + LiteralString: ansi.StylePrimitive{ + Color: hex(charmtone.Cumin), + }, + LiteralStringEscape: ansi.StylePrimitive{ + Color: hex(successSubtle), + }, + GenericDeleted: ansi.StylePrimitive{ + Color: hex(danger), + }, + GenericEmph: ansi.StylePrimitive{ + Italic: new(true), + }, + GenericInserted: ansi.StylePrimitive{ + Color: hex(successMuted), + }, + GenericStrong: ansi.StylePrimitive{ + Bold: new(true), + }, + GenericSubheading: ansi.StylePrimitive{ + Color: hex(fgMuted), + }, + Background: ansi.StylePrimitive{ + BackgroundColor: hex(bgSubtle), + }, + }, + }, + Table: ansi.StyleTable{ + StyleBlock: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{}, + }, + }, + DefinitionDescription: ansi.StylePrimitive{ + BlockPrefix: "\n ", + }, + } + + // QuietMarkdown style - muted colors on subtle background for thinking content. + plainBg := hex(bgBaseLighter) + plainFg := hex(fgMuted) + s.QuietMarkdown = ansi.StyleConfig{ + Document: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: plainFg, + BackgroundColor: plainBg, + }, + }, + BlockQuote: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: plainFg, + BackgroundColor: plainBg, + }, + Indent: new(uint(1)), + IndentToken: new("│ "), + }, + List: ansi.StyleList{ + LevelIndent: defaultListIndent, + }, + Heading: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + BlockSuffix: "\n", + Bold: new(true), + Color: plainFg, + BackgroundColor: plainBg, + }, + }, + H1: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: " ", + Suffix: " ", + Bold: new(true), + Color: plainFg, + BackgroundColor: plainBg, + }, + }, + H2: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "## ", + Color: plainFg, + BackgroundColor: plainBg, + }, + }, + H3: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "### ", + Color: plainFg, + BackgroundColor: plainBg, + }, + }, + H4: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "#### ", + Color: plainFg, + BackgroundColor: plainBg, + }, + }, + H5: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "##### ", + Color: plainFg, + BackgroundColor: plainBg, + }, + }, + H6: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "###### ", + Color: plainFg, + BackgroundColor: plainBg, + }, + }, + Strikethrough: ansi.StylePrimitive{ + CrossedOut: new(true), + Color: plainFg, + BackgroundColor: plainBg, + }, + Emph: ansi.StylePrimitive{ + Italic: new(true), + Color: plainFg, + BackgroundColor: plainBg, + }, + Strong: ansi.StylePrimitive{ + Bold: new(true), + Color: plainFg, + BackgroundColor: plainBg, + }, + HorizontalRule: ansi.StylePrimitive{ + Format: "\n--------\n", + Color: plainFg, + BackgroundColor: plainBg, + }, + Item: ansi.StylePrimitive{ + BlockPrefix: "• ", + Color: plainFg, + BackgroundColor: plainBg, + }, + Enumeration: ansi.StylePrimitive{ + BlockPrefix: ". ", + Color: plainFg, + BackgroundColor: plainBg, + }, + Task: ansi.StyleTask{ + StylePrimitive: ansi.StylePrimitive{ + Color: plainFg, + BackgroundColor: plainBg, + }, + Ticked: "[✓] ", + Unticked: "[ ] ", + }, + Link: ansi.StylePrimitive{ + Underline: new(true), + Color: plainFg, + BackgroundColor: plainBg, + }, + LinkText: ansi.StylePrimitive{ + Bold: new(true), + Color: plainFg, + BackgroundColor: plainBg, + }, + Image: ansi.StylePrimitive{ + Underline: new(true), + Color: plainFg, + BackgroundColor: plainBg, + }, + ImageText: ansi.StylePrimitive{ + Format: "Image: {{.text}} →", + Color: plainFg, + BackgroundColor: plainBg, + }, + Code: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: " ", + Suffix: " ", + Color: plainFg, + BackgroundColor: plainBg, + }, + }, + CodeBlock: ansi.StyleCodeBlock{ + StyleBlock: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: plainFg, + BackgroundColor: plainBg, + }, + Margin: new(uint(defaultMargin)), + }, + }, + Table: ansi.StyleTable{ + StyleBlock: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: plainFg, + BackgroundColor: plainBg, + }, + }, + }, + DefinitionDescription: ansi.StylePrimitive{ + BlockPrefix: "\n ", + Color: plainFg, + BackgroundColor: plainBg, + }, + } + + s.Help = help.Styles{ + ShortKey: base.Foreground(fgMuted), + ShortDesc: base.Foreground(fgSubtle), + ShortSeparator: base.Foreground(border), + Ellipsis: base.Foreground(border), + FullKey: base.Foreground(fgMuted), + FullDesc: base.Foreground(fgSubtle), + FullSeparator: base.Foreground(border), + } + + s.Diff = diffview.Style{ + DividerLine: diffview.LineStyle{ + LineNumber: lipgloss.NewStyle(). + Foreground(fgHalfMuted). + Background(bgBaseLighter), + Code: lipgloss.NewStyle(). + Foreground(fgHalfMuted). + Background(bgBaseLighter), + }, + MissingLine: diffview.LineStyle{ + LineNumber: lipgloss.NewStyle(). + Background(bgBaseLighter), + Code: lipgloss.NewStyle(). + Background(bgBaseLighter), + }, + EqualLine: diffview.LineStyle{ + LineNumber: lipgloss.NewStyle(). + Foreground(fgMuted). + Background(bgBase), + Code: lipgloss.NewStyle(). + Foreground(fgMuted). + Background(bgBase), + }, + InsertLine: diffview.LineStyle{ + LineNumber: lipgloss.NewStyle(). + Foreground(lipgloss.Color("#629657")). + Background(lipgloss.Color("#2b322a")), + Symbol: lipgloss.NewStyle(). + Foreground(lipgloss.Color("#629657")). + Background(lipgloss.Color("#323931")), + Code: lipgloss.NewStyle(). + Background(lipgloss.Color("#323931")), + }, + DeleteLine: diffview.LineStyle{ + LineNumber: lipgloss.NewStyle(). + Foreground(lipgloss.Color("#a45c59")). + Background(lipgloss.Color("#312929")), + Symbol: lipgloss.NewStyle(). + Foreground(lipgloss.Color("#a45c59")). + Background(lipgloss.Color("#383030")), + Code: lipgloss.NewStyle(). + Background(lipgloss.Color("#383030")), + }, + Filename: diffview.LineStyle{ + LineNumber: lipgloss.NewStyle(). + Foreground(fgHalfMuted). + Background(bgBaseLighter), + Code: lipgloss.NewStyle(). + Foreground(fgHalfMuted). + Background(bgBaseLighter), + }, + } + + s.FilePicker = filepicker.Styles{ + DisabledCursor: base.Foreground(fgMuted), + Cursor: base.Foreground(fgBase), + Symlink: base.Foreground(fgSubtle), + Directory: base.Foreground(primary), + File: base.Foreground(fgBase), + DisabledFile: base.Foreground(fgMuted), + DisabledSelected: base.Background(bgOverlay).Foreground(fgMuted), + Permission: base.Foreground(fgMuted), + Selected: base.Background(primary).Foreground(fgBase), + FileSize: base.Foreground(fgMuted), + EmptyDirectory: base.Foreground(fgMuted).PaddingLeft(2).SetString("Empty directory"), + } + + // borders + s.ToolCallSuccess = lipgloss.NewStyle().Foreground(success).SetString(ToolSuccess) + + s.Header.Charm = base.Foreground(secondary) + s.Header.Diagonals = base.Foreground(primary) + s.Header.Percentage = muted + s.Header.Keystroke = muted + s.Header.KeystrokeTip = subtle + s.Header.WorkingDir = muted + s.Header.Separator = subtle + s.Header.Wrapper = lipgloss.NewStyle().Foreground(fgBase) + s.Header.LogoGradCanvas = lipgloss.NewStyle() + s.Header.LogoGradFromColor = secondary + s.Header.LogoGradToColor = primary + + s.CompactDetails.Title = base + s.CompactDetails.View = base.Padding(0, 1, 1, 1).Border(lipgloss.RoundedBorder()).BorderForeground(borderFocus) + s.CompactDetails.Version = lipgloss.NewStyle().Foreground(border) + + // Tool rendering styles + s.Tool.IconPending = base.Foreground(successMuted).SetString(ToolPending) + s.Tool.IconSuccess = base.Foreground(success).SetString(ToolSuccess) + s.Tool.IconError = base.Foreground(error).SetString(ToolError) + s.Tool.IconCancelled = muted.SetString(ToolPending) + + s.Tool.NameNormal = base.Foreground(info) + s.Tool.NameNested = base.Foreground(info) + + s.Tool.ParamMain = subtle + s.Tool.ParamKey = subtle + + // Content rendering - prepared styles that accept width parameter + s.Tool.ContentLine = muted.Background(bgBaseLighter) + s.Tool.ContentTruncation = muted.Background(bgBaseLighter) + s.Tool.ContentCodeLine = base.Background(bgBase).PaddingLeft(2) + s.Tool.ContentCodeTruncation = muted.Background(bgBase).PaddingLeft(2) + s.Tool.ContentCodeBg = bgBase + s.Tool.Body = base.PaddingLeft(2) + + // Deprecated - kept for backward compatibility + s.Tool.ContentBg = muted.Background(bgBaseLighter) + s.Tool.ContentText = muted + s.Tool.ContentLineNumber = base.Foreground(fgMuted).Background(bgBase).PaddingRight(1).PaddingLeft(1) + + s.Tool.StateWaiting = base.Foreground(fgSubtle) + s.Tool.StateCancelled = base.Foreground(fgSubtle) + + s.Tool.ErrorTag = base.Padding(0, 1).Background(danger).Foreground(onAccent) + s.Tool.ErrorMessage = base.Foreground(fgHalfMuted) + + // Diff and multi-edit styles + s.Tool.DiffTruncation = muted.Background(bgBaseLighter).PaddingLeft(2) + s.Tool.NoteTag = base.Padding(0, 1).Background(info).Foreground(onAccent) + s.Tool.NoteMessage = base.Foreground(fgHalfMuted) + + // Job header styles + s.Tool.JobIconPending = base.Foreground(successMuted) + s.Tool.JobIconError = base.Foreground(error) + s.Tool.JobIconSuccess = base.Foreground(success) + s.Tool.JobToolName = base.Foreground(info) + s.Tool.JobAction = base.Foreground(infoMuted) + s.Tool.JobPID = muted + s.Tool.JobDescription = subtle + + // Agent task styles + s.Tool.AgentTaskTag = base.Bold(true).Padding(0, 1).MarginLeft(2).Background(infoSubtle).Foreground(onAccent) + s.Tool.AgentPrompt = muted + + // Agentic fetch styles + s.Tool.AgenticFetchPromptTag = base.Bold(true).Padding(0, 1).MarginLeft(2).Background(success).Foreground(border) + + // Todo styles + s.Tool.TodoRatio = base.Foreground(infoMuted) + s.Tool.TodoCompletedIcon = base.Foreground(success) + s.Tool.TodoInProgressIcon = base.Foreground(successMuted) + s.Tool.TodoPendingIcon = base.Foreground(fgMuted) + s.Tool.TodoStatusNote = lipgloss.NewStyle().Foreground(fgSubtle) + s.Tool.TodoItem = lipgloss.NewStyle().Foreground(fgBase) + s.Tool.TodoJustStarted = lipgloss.NewStyle().Foreground(fgBase) + + // MCP styles + s.Tool.MCPName = base.Foreground(info) + s.Tool.MCPToolName = base.Foreground(infoMuted) + s.Tool.MCPArrow = base.Foreground(info).SetString(ArrowRightIcon) + + // Loading indicators for images, skills + s.Tool.ResourceLoadedText = base.Foreground(success) + s.Tool.ResourceLoadedIndicator = base.Foreground(successMuted) + s.Tool.ResourceName = base + s.Tool.MediaType = base + s.Tool.ResourceSize = base.Foreground(fgMuted) + + // Tool-call action verbs and result-list styling. + s.Tool.ActionCreate = lipgloss.NewStyle().Foreground(successSubtle) + s.Tool.ActionDestroy = lipgloss.NewStyle().Foreground(danger) + s.Tool.ResultEmpty = lipgloss.NewStyle().Foreground(fgSubtle) + s.Tool.ResultTruncation = lipgloss.NewStyle().Foreground(fgSubtle) + s.Tool.ResultItemName = lipgloss.NewStyle().Foreground(fgBase) + s.Tool.ResultItemDesc = lipgloss.NewStyle().Foreground(fgSubtle) + + // Buttons + s.Button.Focused = lipgloss.NewStyle().Foreground(onAccent).Background(secondary) + s.Button.Blurred = lipgloss.NewStyle().Foreground(fgBase).Background(bgSubtle) + + // Editor + s.Editor.PromptNormalFocused = lipgloss.NewStyle().Foreground(successMuted).SetString("::: ") + s.Editor.PromptNormalBlurred = s.Editor.PromptNormalFocused.Foreground(fgMuted) + s.Editor.PromptYoloIconFocused = lipgloss.NewStyle().MarginRight(1).Foreground(fgSubtle).Background(busy).Bold(true).SetString(" ! ") + s.Editor.PromptYoloIconBlurred = s.Editor.PromptYoloIconFocused.Foreground(bgBase).Background(fgMuted) + s.Editor.PromptYoloDotsFocused = lipgloss.NewStyle().MarginRight(1).Foreground(warning).SetString(":::") + s.Editor.PromptYoloDotsBlurred = s.Editor.PromptYoloDotsFocused.Foreground(fgMuted) + + s.Radio.On = lipgloss.NewStyle().Foreground(fgHalfMuted).SetString(RadioOn) + s.Radio.Off = lipgloss.NewStyle().Foreground(fgHalfMuted).SetString(RadioOff) + s.Radio.Label = lipgloss.NewStyle().Foreground(fgHalfMuted) + + // Logo + s.Logo.FieldColor = primary + s.Logo.TitleColorA = secondary + s.Logo.TitleColorB = primary + s.Logo.CharmColor = secondary + s.Logo.VersionColor = primary + s.Logo.SmallCharm = lipgloss.NewStyle().Foreground(secondary) + s.Logo.SmallDiagonals = lipgloss.NewStyle().Foreground(primary) + s.Logo.GradCanvas = lipgloss.NewStyle() + s.Logo.SmallGradFromColor = secondary + s.Logo.SmallGradToColor = primary + + // Section + s.Section.Title = subtle + s.Section.Line = base.Foreground(border) + + // Initialize + s.Initialize.Header = base + s.Initialize.Content = muted + s.Initialize.Accent = base.Foreground(successMuted) + + // ResourceGroup (LSP/MCP/skills sidebar lists). + s.Resource.Heading = lipgloss.NewStyle().Foreground(fgSubtle) + s.Resource.Name = lipgloss.NewStyle().Foreground(fgMuted) + s.Resource.StatusText = lipgloss.NewStyle().Foreground(fgSubtle) + s.Resource.OfflineIcon = lipgloss.NewStyle().Foreground(bgOverlay).SetString("●") + s.Resource.BusyIcon = s.Resource.OfflineIcon.Foreground(busy) + s.Resource.ErrorIcon = s.Resource.OfflineIcon.Foreground(danger) + s.Resource.OnlineIcon = s.Resource.OfflineIcon.Foreground(successMuted) + s.Resource.DisabledIcon = lipgloss.NewStyle().Foreground(fgMuted).SetString("●") + s.Resource.AdditionalText = lipgloss.NewStyle().Foreground(fgSubtle) + s.Resource.CapabilityCount = lipgloss.NewStyle().Foreground(fgSubtle) + s.Resource.RowTitleBase = lipgloss.NewStyle().Foreground(fgBase) + s.Resource.RowDescBase = lipgloss.NewStyle().Foreground(fgBase) + s.Resource.DefaultTitleFg = fgMuted + s.Resource.DefaultDescFg = fgSubtle + + // LSP + s.LSP.ErrorDiagnostic = base.Foreground(error) + s.LSP.WarningDiagnostic = base.Foreground(warning) + s.LSP.HintDiagnostic = base.Foreground(fgHalfMuted) + s.LSP.InfoDiagnostic = base.Foreground(info) + + // Files + s.Files.Path = lipgloss.NewStyle().Foreground(fgMuted) + s.Files.Additions = lipgloss.NewStyle().Foreground(successMuted) + s.Files.Deletions = lipgloss.NewStyle().Foreground(error) + s.Files.SectionTitle = lipgloss.NewStyle().Foreground(fgSubtle) + s.Files.EmptyMessage = lipgloss.NewStyle().Foreground(fgSubtle) + s.Files.TruncationHint = lipgloss.NewStyle().Foreground(fgSubtle) + + // Sidebar + s.Sidebar.SessionTitle = lipgloss.NewStyle().Foreground(fgMuted) + s.Sidebar.WorkingDir = lipgloss.NewStyle().Foreground(fgMuted) + + // ModelInfo + s.ModelInfo.Icon = lipgloss.NewStyle().Foreground(fgSubtle) + s.ModelInfo.Name = lipgloss.NewStyle().Foreground(fgBase) + s.ModelInfo.Provider = lipgloss.NewStyle().Foreground(fgMuted) + s.ModelInfo.ProviderFallback = lipgloss.NewStyle().Foreground(fgMuted).PaddingLeft(2) + s.ModelInfo.Reasoning = lipgloss.NewStyle().Foreground(fgSubtle).PaddingLeft(2) + s.ModelInfo.TokenCount = lipgloss.NewStyle().Foreground(fgSubtle) + s.ModelInfo.TokenPercentage = lipgloss.NewStyle().Foreground(fgMuted) + s.ModelInfo.Cost = lipgloss.NewStyle().Foreground(fgMuted) + + // ResourceGroup + s.Resource.DefaultTitleFg = fgMuted + s.Resource.DefaultDescFg = fgSubtle + + // Chat + messageFocussedBorder := lipgloss.Border{ + Left: "▌", + } + + s.Messages.NoContent = lipgloss.NewStyle().Foreground(fgBase) + s.Messages.UserBlurred = s.Messages.NoContent.PaddingLeft(1).BorderLeft(true). + BorderForeground(primary).BorderStyle(lipgloss.NormalBorder()) + s.Messages.UserFocused = s.Messages.NoContent.PaddingLeft(1).BorderLeft(true). + BorderForeground(primary).BorderStyle(messageFocussedBorder) + s.Messages.AssistantBlurred = s.Messages.NoContent.PaddingLeft(2) + s.Messages.AssistantFocused = s.Messages.NoContent.PaddingLeft(1).BorderLeft(true). + BorderForeground(successMuted).BorderStyle(messageFocussedBorder) + s.Messages.Thinking = lipgloss.NewStyle().MaxHeight(10) + s.Messages.ErrorTag = lipgloss.NewStyle().Padding(0, 1). + Background(danger).Foreground(onAccent) + s.Messages.ErrorTitle = lipgloss.NewStyle().Foreground(fgHalfMuted) + s.Messages.ErrorDetails = lipgloss.NewStyle().Foreground(fgSubtle) + + // Message item styles + s.Messages.ToolCallFocused = muted.PaddingLeft(1). + BorderStyle(messageFocussedBorder). + BorderLeft(true). + BorderForeground(successMuted) + s.Messages.ToolCallBlurred = muted.PaddingLeft(2) + // No padding or border for compact tool calls within messages + s.Messages.ToolCallCompact = muted + s.Messages.SectionHeader = base.PaddingLeft(2) + s.Messages.AssistantInfoIcon = subtle + s.Messages.AssistantInfoModel = muted + s.Messages.AssistantInfoProvider = subtle + s.Messages.AssistantInfoDuration = subtle + s.Messages.AssistantCanceled = lipgloss.NewStyle().Foreground(fgBase).Italic(true) + + // Thinking section styles + s.Messages.ThinkingBox = subtle.Background(bgBaseLighter) + s.Messages.ThinkingTruncationHint = muted + s.Messages.ThinkingFooterTitle = muted + s.Messages.ThinkingFooterDuration = subtle + + // Text selection. + s.TextSelection = lipgloss.NewStyle().Foreground(onPrimary).Background(primary) + + // Dialog styles + s.Dialog.Title = base.Padding(0, 1).Foreground(primary) + s.Dialog.TitleText = base.Foreground(primary) + s.Dialog.TitleError = base.Foreground(danger) + s.Dialog.TitleAccent = base.Foreground(success).Bold(true) + s.Dialog.TitleLineBase = lipgloss.NewStyle() + s.Dialog.TitleGradFromColor = primary + s.Dialog.TitleGradToColor = secondary + + // Dialog.ListItem (commands, reasoning, models) + s.Dialog.ListItem.InfoBlurred = lipgloss.NewStyle().Foreground(fgBase) + s.Dialog.ListItem.InfoFocused = lipgloss.NewStyle().Foreground(fgBase) + + // Dialog.Models + s.Dialog.Models.ConfiguredText = lipgloss.NewStyle().Foreground(fgSubtle) + + // Dialog.Permissions + s.Dialog.Permissions.KeyText = lipgloss.NewStyle().Foreground(fgMuted) + s.Dialog.Permissions.ValueText = lipgloss.NewStyle().Foreground(fgBase) + s.Dialog.Permissions.ParamsBg = bgSubtle + + // Dialog.Quit + s.Dialog.Quit.Content = lipgloss.NewStyle().Foreground(fgBase) + s.Dialog.Quit.Frame = lipgloss.NewStyle().BorderForeground(borderFocus).Border(lipgloss.RoundedBorder()).Padding(1, 2) + s.Dialog.View = base.Border(lipgloss.RoundedBorder()).BorderForeground(borderFocus) + s.Dialog.PrimaryText = base.Padding(0, 1).Foreground(primary) + s.Dialog.SecondaryText = base.Padding(0, 1).Foreground(fgSubtle) + s.Dialog.HelpView = base.Padding(0, 1).AlignHorizontal(lipgloss.Left) + s.Dialog.Help.ShortKey = base.Foreground(fgMuted) + s.Dialog.Help.ShortDesc = base.Foreground(fgSubtle) + s.Dialog.Help.ShortSeparator = base.Foreground(border) + s.Dialog.Help.Ellipsis = base.Foreground(border) + s.Dialog.Help.FullKey = base.Foreground(fgMuted) + s.Dialog.Help.FullDesc = base.Foreground(fgSubtle) + s.Dialog.Help.FullSeparator = base.Foreground(border) + s.Dialog.NormalItem = base.Padding(0, 1).Foreground(fgBase) + s.Dialog.SelectedItem = base.Padding(0, 1).Background(primary).Foreground(fgBase) + s.Dialog.InputPrompt = base.Margin(1, 1) + + s.Dialog.List = base.Margin(0, 0, 1, 0) + s.Dialog.ContentPanel = base.Background(bgSubtle).Foreground(fgBase).Padding(1, 2) + s.Dialog.Spinner = base.Foreground(secondary) + s.Dialog.ScrollbarThumb = base.Foreground(secondary) + s.Dialog.ScrollbarTrack = base.Foreground(border) + + s.Dialog.ImagePreview = lipgloss.NewStyle().Padding(0, 1).Foreground(fgSubtle) + + // API key input dialog + s.Dialog.APIKey.Spinner = base.Foreground(success) + + // OAuth dialog + s.Dialog.OAuth.Spinner = base.Foreground(successSubtle) + s.Dialog.OAuth.Instructions = lipgloss.NewStyle().Foreground(onAccent) + s.Dialog.OAuth.UserCode = lipgloss.NewStyle().Bold(true).Foreground(onAccent) + s.Dialog.OAuth.Success = lipgloss.NewStyle().Foreground(successSubtle) + s.Dialog.OAuth.Link = lipgloss.NewStyle().Foreground(successMuted).Underline(true) + s.Dialog.OAuth.Enter = lipgloss.NewStyle().Foreground(primary) + s.Dialog.OAuth.ErrorText = lipgloss.NewStyle().Foreground(error) + s.Dialog.OAuth.StatusText = lipgloss.NewStyle().Foreground(fgMuted) + s.Dialog.OAuth.UserCodeBg = bgBaseLighter + + s.Dialog.Arguments.Content = base.Padding(1) + s.Dialog.Arguments.Description = base.MarginBottom(1).MaxHeight(3) + s.Dialog.Arguments.InputLabelBlurred = base.Foreground(fgMuted) + s.Dialog.Arguments.InputLabelFocused = base.Bold(true) + s.Dialog.Arguments.InputRequiredMarkBlurred = base.Foreground(fgMuted).SetString("*") + s.Dialog.Arguments.InputRequiredMarkFocused = base.Foreground(primary).Bold(true).SetString("*") + + s.Dialog.Sessions.DeletingTitle = s.Dialog.Title.Foreground(danger) + s.Dialog.Sessions.DeletingView = s.Dialog.View.BorderForeground(danger) + s.Dialog.Sessions.DeletingMessage = base.Padding(1) + s.Dialog.Sessions.DeletingTitleGradientFromColor = danger + s.Dialog.Sessions.DeletingTitleGradientToColor = primary + s.Dialog.Sessions.DeletingItemBlurred = s.Dialog.NormalItem.Foreground(fgSubtle) + s.Dialog.Sessions.DeletingItemFocused = s.Dialog.SelectedItem.Background(danger).Foreground(onAccent) + + s.Dialog.Sessions.RenamingingTitle = s.Dialog.Title.Foreground(warning) + s.Dialog.Sessions.RenamingView = s.Dialog.View.BorderForeground(warning) + s.Dialog.Sessions.RenamingingMessage = base.Padding(1) + s.Dialog.Sessions.RenamingTitleGradientFromColor = warning + s.Dialog.Sessions.RenamingTitleGradientToColor = tertiary + s.Dialog.Sessions.RenamingItemBlurred = s.Dialog.NormalItem.Foreground(fgSubtle) + s.Dialog.Sessions.RenamingingItemFocused = s.Dialog.SelectedItem.UnsetBackground().UnsetForeground() + s.Dialog.Sessions.RenamingPlaceholder = base.Foreground(fgMuted) + s.Dialog.Sessions.InfoBlurred = lipgloss.NewStyle().Foreground(fgSubtle) + s.Dialog.Sessions.InfoFocused = lipgloss.NewStyle().Foreground(fgBase) + + s.Status.Help = lipgloss.NewStyle().Padding(0, 1) + s.Status.SuccessIndicator = base.Foreground(bgSubtle).Background(success).Padding(0, 1).Bold(true).SetString("OKAY!") + s.Status.InfoIndicator = s.Status.SuccessIndicator + s.Status.UpdateIndicator = s.Status.SuccessIndicator.SetString("HEY!") + s.Status.WarnIndicator = s.Status.SuccessIndicator.Foreground(bgOverlay).Background(warningStrong).SetString("WARNING") + s.Status.ErrorIndicator = s.Status.SuccessIndicator.Foreground(bgBase).Background(danger).SetString("ERROR") + s.Status.SuccessMessage = base.Foreground(bgSubtle).Background(successMuted).Padding(0, 1) + s.Status.InfoMessage = s.Status.SuccessMessage + s.Status.UpdateMessage = s.Status.SuccessMessage + s.Status.WarnMessage = s.Status.SuccessMessage.Foreground(bgOverlay).Background(warning) + s.Status.ErrorMessage = s.Status.SuccessMessage.Foreground(onAccent).Background(error) + + // Completions styles + s.Completions.Normal = base.Background(bgSubtle).Foreground(fgBase) + s.Completions.Focused = base.Background(primary).Foreground(onAccent) + s.Completions.Match = base.Underline(true) + + // Attachments styles + attachmentIconStyle := base.Foreground(bgSubtle).Background(success).Padding(0, 1) + s.Attachments.Image = attachmentIconStyle.SetString(ImageIcon) + s.Attachments.Text = attachmentIconStyle.SetString(TextIcon) + s.Attachments.Normal = base.Padding(0, 1).MarginRight(1).Background(fgMuted).Foreground(fgBase) + s.Attachments.Deleting = base.Padding(0, 1).Bold(true).Background(danger).Foreground(fgBase) + + // Pills styles + s.Pills.Base = base.Padding(0, 1) + s.Pills.Focused = base.Padding(0, 1).BorderStyle(lipgloss.RoundedBorder()).BorderForeground(bgOverlay) + s.Pills.Blurred = base.Padding(0, 1).BorderStyle(lipgloss.HiddenBorder()) + s.Pills.QueueItemPrefix = lipgloss.NewStyle().Foreground(fgMuted).SetString(" •") + s.Pills.QueueItemText = lipgloss.NewStyle().Foreground(fgMuted) + s.Pills.QueueLabel = lipgloss.NewStyle().Foreground(fgBase) + s.Pills.QueueIconBase = lipgloss.NewStyle().Foreground(fgBase) + s.Pills.QueueGradFromColor = error + s.Pills.QueueGradToColor = secondary + s.Pills.TodoLabel = lipgloss.NewStyle().Foreground(fgBase) + s.Pills.TodoProgress = lipgloss.NewStyle().Foreground(fgMuted) + s.Pills.TodoCurrentTask = lipgloss.NewStyle().Foreground(fgSubtle) + s.Pills.TodoSpinner = lipgloss.NewStyle().Foreground(successMuted) + s.Pills.HelpKey = lipgloss.NewStyle().Foreground(fgMuted) + s.Pills.HelpText = lipgloss.NewStyle().Foreground(fgSubtle) + s.Pills.Area = base + + return s +} diff --git a/internal/ui/styles/styles.go b/internal/ui/styles/styles.go index 51835ea2ebf52fce74daee6414cdcc4ba72c6279..ecde74258c127992e5a995adf74a69588cefe7a3 100644 --- a/internal/ui/styles/styles.go +++ b/internal/ui/styles/styles.go @@ -1,6 +1,8 @@ +// Package styles define styling and theming for the project. package styles import ( + "fmt" "image/color" "strings" @@ -8,12 +10,10 @@ import ( "charm.land/bubbles/v2/help" "charm.land/bubbles/v2/textarea" "charm.land/bubbles/v2/textinput" - tea "charm.land/bubbletea/v2" "charm.land/glamour/v2/ansi" "charm.land/lipgloss/v2" "github.com/alecthomas/chroma/v2" "github.com/charmbracelet/crush/internal/ui/diffview" - "github.com/charmbracelet/x/exp/charmtone" ) const ( @@ -566,938 +566,13 @@ func (s *Styles) DialogHelpStyles() help.Styles { return help.Styles(s.Dialog.Help) } -// DefaultStyles returns the default styles for the UI. -func DefaultStyles() Styles { - var ( - primary = charmtone.Charple - secondary = charmtone.Dolly - tertiary = charmtone.Bok - // accent = charmtone.Zest - - // Backgrounds - bgBase = charmtone.Pepper - bgBaseLighter = charmtone.BBQ - bgSubtle = charmtone.Charcoal - bgOverlay = charmtone.Iron - - // Foregrounds - fgBase = charmtone.Ash - fgMuted = charmtone.Squid - fgHalfMuted = charmtone.Smoke - fgSubtle = charmtone.Oyster - // fgSelected = charmtone.Salt - - // Borders - border = charmtone.Charcoal - borderFocus = charmtone.Charple - - // Status - error = charmtone.Sriracha - warning = charmtone.Zest - info = charmtone.Malibu - - // Colors - white = charmtone.Butter - - blueLight = charmtone.Sardine - blue = charmtone.Malibu - blueDark = charmtone.Damson - - // yellow = charmtone.Mustard - yellow = charmtone.Mustard - // citron = charmtone.Citron - - greenLight = charmtone.Bok - green = charmtone.Julep - greenDark = charmtone.Guac - // greenLight = charmtone.Bok - - red = charmtone.Coral - redDark = charmtone.Sriracha - // redLight = charmtone.Salmon - // cherry = charmtone.Cherry - ) - - normalBorder := lipgloss.NormalBorder() - - base := lipgloss.NewStyle().Foreground(fgBase) - muted := lipgloss.NewStyle().Foreground(fgMuted) - subtle := lipgloss.NewStyle().Foreground(fgSubtle) - - s := Styles{} - - s.Background = bgBase - - // Populate color fields - s.WorkingGradFromColor = primary - s.WorkingGradToColor = secondary - s.WorkingLabelColor = fgBase - - s.TextInput = textinput.Styles{ - Focused: textinput.StyleState{ - Text: base, - Placeholder: base.Foreground(fgSubtle), - Prompt: base.Foreground(tertiary), - Suggestion: base.Foreground(fgSubtle), - }, - Blurred: textinput.StyleState{ - Text: base.Foreground(fgMuted), - Placeholder: base.Foreground(fgSubtle), - Prompt: base.Foreground(fgMuted), - Suggestion: base.Foreground(fgSubtle), - }, - Cursor: textinput.CursorStyle{ - Color: secondary, - Shape: tea.CursorBlock, - Blink: true, - }, - } - - s.Editor.Textarea = textarea.Styles{ - Focused: textarea.StyleState{ - Base: base, - Text: base, - LineNumber: base.Foreground(fgSubtle), - CursorLine: base, - CursorLineNumber: base.Foreground(fgSubtle), - Placeholder: base.Foreground(fgSubtle), - Prompt: base.Foreground(tertiary), - }, - Blurred: textarea.StyleState{ - Base: base, - Text: base.Foreground(fgMuted), - LineNumber: base.Foreground(fgMuted), - CursorLine: base, - CursorLineNumber: base.Foreground(fgMuted), - Placeholder: base.Foreground(fgSubtle), - Prompt: base.Foreground(fgMuted), - }, - Cursor: textarea.CursorStyle{ - Color: secondary, - Shape: tea.CursorBlock, - Blink: true, - }, - } - - s.Markdown = ansi.StyleConfig{ - Document: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - // BlockPrefix: "\n", - // BlockSuffix: "\n", - Color: new(charmtone.Smoke.Hex()), - }, - // Margin: new(uint(defaultMargin)), - }, - BlockQuote: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{}, - Indent: new(uint(1)), - IndentToken: new("│ "), - }, - List: ansi.StyleList{ - LevelIndent: defaultListIndent, - }, - Heading: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - BlockSuffix: "\n", - Color: new(charmtone.Malibu.Hex()), - Bold: new(true), - }, - }, - H1: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: " ", - Suffix: " ", - Color: new(charmtone.Zest.Hex()), - BackgroundColor: new(charmtone.Charple.Hex()), - Bold: new(true), - }, - }, - H2: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "## ", - }, - }, - H3: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "### ", - }, - }, - H4: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "#### ", - }, - }, - H5: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "##### ", - }, - }, - H6: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "###### ", - Color: new(charmtone.Guac.Hex()), - Bold: new(false), - }, - }, - Strikethrough: ansi.StylePrimitive{ - CrossedOut: new(true), - }, - Emph: ansi.StylePrimitive{ - Italic: new(true), - }, - Strong: ansi.StylePrimitive{ - Bold: new(true), - }, - HorizontalRule: ansi.StylePrimitive{ - Color: new(charmtone.Charcoal.Hex()), - Format: "\n--------\n", - }, - Item: ansi.StylePrimitive{ - BlockPrefix: "• ", - }, - Enumeration: ansi.StylePrimitive{ - BlockPrefix: ". ", - }, - Task: ansi.StyleTask{ - StylePrimitive: ansi.StylePrimitive{}, - Ticked: "[✓] ", - Unticked: "[ ] ", - }, - Link: ansi.StylePrimitive{ - Color: new(charmtone.Zinc.Hex()), - Underline: new(true), - }, - LinkText: ansi.StylePrimitive{ - Color: new(charmtone.Guac.Hex()), - Bold: new(true), - }, - Image: ansi.StylePrimitive{ - Color: new(charmtone.Cheeky.Hex()), - Underline: new(true), - }, - ImageText: ansi.StylePrimitive{ - Color: new(charmtone.Squid.Hex()), - Format: "Image: {{.text}} →", - }, - Code: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: " ", - Suffix: " ", - Color: new(charmtone.Coral.Hex()), - BackgroundColor: new(charmtone.Charcoal.Hex()), - }, - }, - CodeBlock: ansi.StyleCodeBlock{ - StyleBlock: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Color: new(charmtone.Charcoal.Hex()), - }, - Margin: new(uint(defaultMargin)), - }, - Chroma: &ansi.Chroma{ - Text: ansi.StylePrimitive{ - Color: new(charmtone.Smoke.Hex()), - }, - Error: ansi.StylePrimitive{ - Color: new(charmtone.Butter.Hex()), - BackgroundColor: new(charmtone.Sriracha.Hex()), - }, - Comment: ansi.StylePrimitive{ - Color: new(charmtone.Oyster.Hex()), - }, - CommentPreproc: ansi.StylePrimitive{ - Color: new(charmtone.Bengal.Hex()), - }, - Keyword: ansi.StylePrimitive{ - Color: new(charmtone.Malibu.Hex()), - }, - KeywordReserved: ansi.StylePrimitive{ - Color: new(charmtone.Pony.Hex()), - }, - KeywordNamespace: ansi.StylePrimitive{ - Color: new(charmtone.Pony.Hex()), - }, - KeywordType: ansi.StylePrimitive{ - Color: new(charmtone.Guppy.Hex()), - }, - Operator: ansi.StylePrimitive{ - Color: new(charmtone.Salmon.Hex()), - }, - Punctuation: ansi.StylePrimitive{ - Color: new(charmtone.Zest.Hex()), - }, - Name: ansi.StylePrimitive{ - Color: new(charmtone.Smoke.Hex()), - }, - NameBuiltin: ansi.StylePrimitive{ - Color: new(charmtone.Cheeky.Hex()), - }, - NameTag: ansi.StylePrimitive{ - Color: new(charmtone.Mauve.Hex()), - }, - NameAttribute: ansi.StylePrimitive{ - Color: new(charmtone.Hazy.Hex()), - }, - NameClass: ansi.StylePrimitive{ - Color: new(charmtone.Salt.Hex()), - Underline: new(true), - Bold: new(true), - }, - NameDecorator: ansi.StylePrimitive{ - Color: new(charmtone.Citron.Hex()), - }, - NameFunction: ansi.StylePrimitive{ - Color: new(charmtone.Guac.Hex()), - }, - LiteralNumber: ansi.StylePrimitive{ - Color: new(charmtone.Julep.Hex()), - }, - LiteralString: ansi.StylePrimitive{ - Color: new(charmtone.Cumin.Hex()), - }, - LiteralStringEscape: ansi.StylePrimitive{ - Color: new(charmtone.Bok.Hex()), - }, - GenericDeleted: ansi.StylePrimitive{ - Color: new(charmtone.Coral.Hex()), - }, - GenericEmph: ansi.StylePrimitive{ - Italic: new(true), - }, - GenericInserted: ansi.StylePrimitive{ - Color: new(charmtone.Guac.Hex()), - }, - GenericStrong: ansi.StylePrimitive{ - Bold: new(true), - }, - GenericSubheading: ansi.StylePrimitive{ - Color: new(charmtone.Squid.Hex()), - }, - Background: ansi.StylePrimitive{ - BackgroundColor: new(charmtone.Charcoal.Hex()), - }, - }, - }, - Table: ansi.StyleTable{ - StyleBlock: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{}, - }, - }, - DefinitionDescription: ansi.StylePrimitive{ - BlockPrefix: "\n ", - }, - } - - // QuietMarkdown style - muted colors on subtle background for thinking content. - plainBg := new(bgBaseLighter.Hex()) - plainFg := new(fgMuted.Hex()) - s.QuietMarkdown = ansi.StyleConfig{ - Document: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Color: plainFg, - BackgroundColor: plainBg, - }, - }, - BlockQuote: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Color: plainFg, - BackgroundColor: plainBg, - }, - Indent: new(uint(1)), - IndentToken: new("│ "), - }, - List: ansi.StyleList{ - LevelIndent: defaultListIndent, - }, - Heading: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - BlockSuffix: "\n", - Bold: new(true), - Color: plainFg, - BackgroundColor: plainBg, - }, - }, - H1: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: " ", - Suffix: " ", - Bold: new(true), - Color: plainFg, - BackgroundColor: plainBg, - }, - }, - H2: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "## ", - Color: plainFg, - BackgroundColor: plainBg, - }, - }, - H3: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "### ", - Color: plainFg, - BackgroundColor: plainBg, - }, - }, - H4: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "#### ", - Color: plainFg, - BackgroundColor: plainBg, - }, - }, - H5: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "##### ", - Color: plainFg, - BackgroundColor: plainBg, - }, - }, - H6: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "###### ", - Color: plainFg, - BackgroundColor: plainBg, - }, - }, - Strikethrough: ansi.StylePrimitive{ - CrossedOut: new(true), - Color: plainFg, - BackgroundColor: plainBg, - }, - Emph: ansi.StylePrimitive{ - Italic: new(true), - Color: plainFg, - BackgroundColor: plainBg, - }, - Strong: ansi.StylePrimitive{ - Bold: new(true), - Color: plainFg, - BackgroundColor: plainBg, - }, - HorizontalRule: ansi.StylePrimitive{ - Format: "\n--------\n", - Color: plainFg, - BackgroundColor: plainBg, - }, - Item: ansi.StylePrimitive{ - BlockPrefix: "• ", - Color: plainFg, - BackgroundColor: plainBg, - }, - Enumeration: ansi.StylePrimitive{ - BlockPrefix: ". ", - Color: plainFg, - BackgroundColor: plainBg, - }, - Task: ansi.StyleTask{ - StylePrimitive: ansi.StylePrimitive{ - Color: plainFg, - BackgroundColor: plainBg, - }, - Ticked: "[✓] ", - Unticked: "[ ] ", - }, - Link: ansi.StylePrimitive{ - Underline: new(true), - Color: plainFg, - BackgroundColor: plainBg, - }, - LinkText: ansi.StylePrimitive{ - Bold: new(true), - Color: plainFg, - BackgroundColor: plainBg, - }, - Image: ansi.StylePrimitive{ - Underline: new(true), - Color: plainFg, - BackgroundColor: plainBg, - }, - ImageText: ansi.StylePrimitive{ - Format: "Image: {{.text}} →", - Color: plainFg, - BackgroundColor: plainBg, - }, - Code: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: " ", - Suffix: " ", - Color: plainFg, - BackgroundColor: plainBg, - }, - }, - CodeBlock: ansi.StyleCodeBlock{ - StyleBlock: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Color: plainFg, - BackgroundColor: plainBg, - }, - Margin: new(uint(defaultMargin)), - }, - }, - Table: ansi.StyleTable{ - StyleBlock: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Color: plainFg, - BackgroundColor: plainBg, - }, - }, - }, - DefinitionDescription: ansi.StylePrimitive{ - BlockPrefix: "\n ", - Color: plainFg, - BackgroundColor: plainBg, - }, - } - - s.Help = help.Styles{ - ShortKey: base.Foreground(fgMuted), - ShortDesc: base.Foreground(fgSubtle), - ShortSeparator: base.Foreground(border), - Ellipsis: base.Foreground(border), - FullKey: base.Foreground(fgMuted), - FullDesc: base.Foreground(fgSubtle), - FullSeparator: base.Foreground(border), - } - - s.Diff = diffview.Style{ - DividerLine: diffview.LineStyle{ - LineNumber: lipgloss.NewStyle(). - Foreground(fgHalfMuted). - Background(bgBaseLighter), - Code: lipgloss.NewStyle(). - Foreground(fgHalfMuted). - Background(bgBaseLighter), - }, - MissingLine: diffview.LineStyle{ - LineNumber: lipgloss.NewStyle(). - Background(bgBaseLighter), - Code: lipgloss.NewStyle(). - Background(bgBaseLighter), - }, - EqualLine: diffview.LineStyle{ - LineNumber: lipgloss.NewStyle(). - Foreground(fgMuted). - Background(bgBase), - Code: lipgloss.NewStyle(). - Foreground(fgMuted). - Background(bgBase), - }, - InsertLine: diffview.LineStyle{ - LineNumber: lipgloss.NewStyle(). - Foreground(lipgloss.Color("#629657")). - Background(lipgloss.Color("#2b322a")), - Symbol: lipgloss.NewStyle(). - Foreground(lipgloss.Color("#629657")). - Background(lipgloss.Color("#323931")), - Code: lipgloss.NewStyle(). - Background(lipgloss.Color("#323931")), - }, - DeleteLine: diffview.LineStyle{ - LineNumber: lipgloss.NewStyle(). - Foreground(lipgloss.Color("#a45c59")). - Background(lipgloss.Color("#312929")), - Symbol: lipgloss.NewStyle(). - Foreground(lipgloss.Color("#a45c59")). - Background(lipgloss.Color("#383030")), - Code: lipgloss.NewStyle(). - Background(lipgloss.Color("#383030")), - }, - Filename: diffview.LineStyle{ - LineNumber: lipgloss.NewStyle(). - Foreground(fgHalfMuted). - Background(bgBaseLighter), - Code: lipgloss.NewStyle(). - Foreground(fgHalfMuted). - Background(bgBaseLighter), - }, - } - - s.FilePicker = filepicker.Styles{ - DisabledCursor: base.Foreground(fgMuted), - Cursor: base.Foreground(fgBase), - Symlink: base.Foreground(fgSubtle), - Directory: base.Foreground(primary), - File: base.Foreground(fgBase), - DisabledFile: base.Foreground(fgMuted), - DisabledSelected: base.Background(bgOverlay).Foreground(fgMuted), - Permission: base.Foreground(fgMuted), - Selected: base.Background(primary).Foreground(fgBase), - FileSize: base.Foreground(fgMuted), - EmptyDirectory: base.Foreground(fgMuted).PaddingLeft(2).SetString("Empty directory"), - } - - // borders - s.ToolCallSuccess = lipgloss.NewStyle().Foreground(green).SetString(ToolSuccess) - - s.Header.Charm = base.Foreground(secondary) - s.Header.Diagonals = base.Foreground(primary) - s.Header.Percentage = muted - s.Header.Keystroke = muted - s.Header.KeystrokeTip = subtle - s.Header.WorkingDir = muted - s.Header.Separator = subtle - s.Header.Wrapper = lipgloss.NewStyle().Foreground(fgBase) - s.Header.LogoGradCanvas = lipgloss.NewStyle() - s.Header.LogoGradFromColor = secondary - s.Header.LogoGradToColor = primary - - s.CompactDetails.Title = base - s.CompactDetails.View = base.Padding(0, 1, 1, 1).Border(lipgloss.RoundedBorder()).BorderForeground(borderFocus) - s.CompactDetails.Version = lipgloss.NewStyle().Foreground(border) - - // Tool rendering styles - s.Tool.IconPending = base.Foreground(greenDark).SetString(ToolPending) - s.Tool.IconSuccess = base.Foreground(green).SetString(ToolSuccess) - s.Tool.IconError = base.Foreground(redDark).SetString(ToolError) - s.Tool.IconCancelled = muted.SetString(ToolPending) - - s.Tool.NameNormal = base.Foreground(blue) - s.Tool.NameNested = base.Foreground(blue) - - s.Tool.ParamMain = subtle - s.Tool.ParamKey = subtle - - // Content rendering - prepared styles that accept width parameter - s.Tool.ContentLine = muted.Background(bgBaseLighter) - s.Tool.ContentTruncation = muted.Background(bgBaseLighter) - s.Tool.ContentCodeLine = base.Background(bgBase).PaddingLeft(2) - s.Tool.ContentCodeTruncation = muted.Background(bgBase).PaddingLeft(2) - s.Tool.ContentCodeBg = bgBase - s.Tool.Body = base.PaddingLeft(2) - - // Deprecated - kept for backward compatibility - s.Tool.ContentBg = muted.Background(bgBaseLighter) - s.Tool.ContentText = muted - s.Tool.ContentLineNumber = base.Foreground(fgMuted).Background(bgBase).PaddingRight(1).PaddingLeft(1) - - s.Tool.StateWaiting = base.Foreground(fgSubtle) - s.Tool.StateCancelled = base.Foreground(fgSubtle) - - s.Tool.ErrorTag = base.Padding(0, 1).Background(red).Foreground(white) - s.Tool.ErrorMessage = base.Foreground(fgHalfMuted) - - // Diff and multi-edit styles - s.Tool.DiffTruncation = muted.Background(bgBaseLighter).PaddingLeft(2) - s.Tool.NoteTag = base.Padding(0, 1).Background(info).Foreground(white) - s.Tool.NoteMessage = base.Foreground(fgHalfMuted) - - // Job header styles - s.Tool.JobIconPending = base.Foreground(greenDark) - s.Tool.JobIconError = base.Foreground(redDark) - s.Tool.JobIconSuccess = base.Foreground(green) - s.Tool.JobToolName = base.Foreground(blue) - s.Tool.JobAction = base.Foreground(blueDark) - s.Tool.JobPID = muted - s.Tool.JobDescription = subtle - - // Agent task styles - s.Tool.AgentTaskTag = base.Bold(true).Padding(0, 1).MarginLeft(2).Background(blueLight).Foreground(white) - s.Tool.AgentPrompt = muted - - // Agentic fetch styles - s.Tool.AgenticFetchPromptTag = base.Bold(true).Padding(0, 1).MarginLeft(2).Background(green).Foreground(border) - - // Todo styles - s.Tool.TodoRatio = base.Foreground(blueDark) - s.Tool.TodoCompletedIcon = base.Foreground(green) - s.Tool.TodoInProgressIcon = base.Foreground(greenDark) - s.Tool.TodoPendingIcon = base.Foreground(fgMuted) - s.Tool.TodoStatusNote = lipgloss.NewStyle().Foreground(fgSubtle) - s.Tool.TodoItem = lipgloss.NewStyle().Foreground(fgBase) - s.Tool.TodoJustStarted = lipgloss.NewStyle().Foreground(fgBase) - - // MCP styles - s.Tool.MCPName = base.Foreground(blue) - s.Tool.MCPToolName = base.Foreground(blueDark) - s.Tool.MCPArrow = base.Foreground(blue).SetString(ArrowRightIcon) - - // Loading indicators for images, skills - s.Tool.ResourceLoadedText = base.Foreground(green) - s.Tool.ResourceLoadedIndicator = base.Foreground(greenDark) - s.Tool.ResourceName = base - s.Tool.MediaType = base - s.Tool.ResourceSize = base.Foreground(fgMuted) - - // Tool-call action verbs and result-list styling. - s.Tool.ActionCreate = lipgloss.NewStyle().Foreground(greenLight) - s.Tool.ActionDestroy = lipgloss.NewStyle().Foreground(red) - s.Tool.ResultEmpty = lipgloss.NewStyle().Foreground(fgSubtle) - s.Tool.ResultTruncation = lipgloss.NewStyle().Foreground(fgSubtle) - s.Tool.ResultItemName = lipgloss.NewStyle().Foreground(fgBase) - s.Tool.ResultItemDesc = lipgloss.NewStyle().Foreground(fgSubtle) - - // Hook styles - s.Tool.HookLabel = base.Foreground(green) - s.Tool.HookName = base - s.Tool.HookMatcher = base.Foreground(fgMuted) - s.Tool.HookArrow = base.Foreground(green) - s.Tool.HookDetail = base.Foreground(fgMuted) - s.Tool.HookOK = base.Foreground(charmtone.Guac) - s.Tool.HookDenied = base.Foreground(charmtone.Sriracha) - s.Tool.HookDeniedLabel = base.Foreground(red) - s.Tool.HookDeniedReason = base.Foreground(charmtone.Iron) - s.Tool.HookRewrote = base.Foreground(charmtone.Iron) - - // Buttons - s.Button.Focused = lipgloss.NewStyle().Foreground(white).Background(secondary) - s.Button.Blurred = lipgloss.NewStyle().Foreground(fgBase).Background(bgSubtle) - - // Editor - s.Editor.PromptNormalFocused = lipgloss.NewStyle().Foreground(greenDark).SetString("::: ") - s.Editor.PromptNormalBlurred = s.Editor.PromptNormalFocused.Foreground(fgMuted) - s.Editor.PromptYoloIconFocused = lipgloss.NewStyle().MarginRight(1).Foreground(charmtone.Oyster).Background(charmtone.Citron).Bold(true).SetString(" ! ") - s.Editor.PromptYoloIconBlurred = s.Editor.PromptYoloIconFocused.Foreground(charmtone.Pepper).Background(charmtone.Squid) - s.Editor.PromptYoloDotsFocused = lipgloss.NewStyle().MarginRight(1).Foreground(charmtone.Zest).SetString(":::") - s.Editor.PromptYoloDotsBlurred = s.Editor.PromptYoloDotsFocused.Foreground(charmtone.Squid) - - s.Radio.On = lipgloss.NewStyle().Foreground(fgHalfMuted).SetString(RadioOn) - s.Radio.Off = lipgloss.NewStyle().Foreground(fgHalfMuted).SetString(RadioOff) - s.Radio.Label = lipgloss.NewStyle().Foreground(fgHalfMuted) - - // Logo - s.Logo.FieldColor = primary - s.Logo.TitleColorA = secondary - s.Logo.TitleColorB = primary - s.Logo.CharmColor = secondary - s.Logo.VersionColor = primary - s.Logo.SmallCharm = lipgloss.NewStyle().Foreground(secondary) - s.Logo.SmallDiagonals = lipgloss.NewStyle().Foreground(primary) - s.Logo.GradCanvas = lipgloss.NewStyle() - s.Logo.SmallGradFromColor = secondary - s.Logo.SmallGradToColor = primary - - // Section - s.Section.Title = subtle - s.Section.Line = base.Foreground(charmtone.Charcoal) - - // Initialize - s.Initialize.Header = base - s.Initialize.Content = muted - s.Initialize.Accent = base.Foreground(greenDark) - - // ResourceGroup (LSP/MCP/skills sidebar lists). - s.Resource.Heading = lipgloss.NewStyle().Foreground(charmtone.Oyster) - s.Resource.Name = lipgloss.NewStyle().Foreground(charmtone.Squid) - s.Resource.StatusText = lipgloss.NewStyle().Foreground(charmtone.Oyster) - s.Resource.OfflineIcon = lipgloss.NewStyle().Foreground(charmtone.Iron).SetString("●") - s.Resource.BusyIcon = s.Resource.OfflineIcon.Foreground(charmtone.Citron) - s.Resource.ErrorIcon = s.Resource.OfflineIcon.Foreground(charmtone.Coral) - s.Resource.OnlineIcon = s.Resource.OfflineIcon.Foreground(charmtone.Guac) - s.Resource.DisabledIcon = lipgloss.NewStyle().Foreground(fgMuted).SetString("●") - s.Resource.AdditionalText = lipgloss.NewStyle().Foreground(charmtone.Oyster) - s.Resource.CapabilityCount = lipgloss.NewStyle().Foreground(fgSubtle) - s.Resource.RowTitleBase = lipgloss.NewStyle().Foreground(fgBase) - s.Resource.RowDescBase = lipgloss.NewStyle().Foreground(fgBase) - s.Resource.DefaultTitleFg = fgMuted - s.Resource.DefaultDescFg = fgSubtle - - // LSP - s.LSP.ErrorDiagnostic = base.Foreground(redDark) - s.LSP.WarningDiagnostic = base.Foreground(warning) - s.LSP.HintDiagnostic = base.Foreground(fgHalfMuted) - s.LSP.InfoDiagnostic = base.Foreground(info) - - // Files - s.Files.Path = lipgloss.NewStyle().Foreground(fgMuted) - s.Files.Additions = lipgloss.NewStyle().Foreground(greenDark) - s.Files.Deletions = lipgloss.NewStyle().Foreground(redDark) - s.Files.SectionTitle = lipgloss.NewStyle().Foreground(fgSubtle) - s.Files.EmptyMessage = lipgloss.NewStyle().Foreground(fgSubtle) - s.Files.TruncationHint = lipgloss.NewStyle().Foreground(fgSubtle) - - // Sidebar - s.Sidebar.SessionTitle = lipgloss.NewStyle().Foreground(fgMuted) - s.Sidebar.WorkingDir = lipgloss.NewStyle().Foreground(fgMuted) - - // ModelInfo - s.ModelInfo.Icon = lipgloss.NewStyle().Foreground(fgSubtle) - s.ModelInfo.Name = lipgloss.NewStyle().Foreground(fgBase) - s.ModelInfo.Provider = lipgloss.NewStyle().Foreground(fgMuted) - s.ModelInfo.ProviderFallback = lipgloss.NewStyle().Foreground(fgMuted).PaddingLeft(2) - s.ModelInfo.Reasoning = lipgloss.NewStyle().Foreground(fgSubtle).PaddingLeft(2) - s.ModelInfo.TokenCount = lipgloss.NewStyle().Foreground(fgSubtle) - s.ModelInfo.TokenPercentage = lipgloss.NewStyle().Foreground(fgMuted) - s.ModelInfo.Cost = lipgloss.NewStyle().Foreground(fgMuted) - - // ResourceGroup - s.Resource.DefaultTitleFg = fgMuted - s.Resource.DefaultDescFg = fgSubtle - - // Chat - messageFocussedBorder := lipgloss.Border{ - Left: "▌", - } - - s.Messages.NoContent = lipgloss.NewStyle().Foreground(fgBase) - s.Messages.UserBlurred = s.Messages.NoContent.PaddingLeft(1).BorderLeft(true). - BorderForeground(primary).BorderStyle(normalBorder) - s.Messages.UserFocused = s.Messages.NoContent.PaddingLeft(1).BorderLeft(true). - BorderForeground(primary).BorderStyle(messageFocussedBorder) - s.Messages.AssistantBlurred = s.Messages.NoContent.PaddingLeft(2) - s.Messages.AssistantFocused = s.Messages.NoContent.PaddingLeft(1).BorderLeft(true). - BorderForeground(greenDark).BorderStyle(messageFocussedBorder) - s.Messages.Thinking = lipgloss.NewStyle().MaxHeight(10) - s.Messages.ErrorTag = lipgloss.NewStyle().Padding(0, 1). - Background(red).Foreground(white) - s.Messages.ErrorTitle = lipgloss.NewStyle().Foreground(fgHalfMuted) - s.Messages.ErrorDetails = lipgloss.NewStyle().Foreground(fgSubtle) - - // Message item styles - s.Messages.ToolCallFocused = muted.PaddingLeft(1). - BorderStyle(messageFocussedBorder). - BorderLeft(true). - BorderForeground(greenDark) - s.Messages.ToolCallBlurred = muted.PaddingLeft(2) - // No padding or border for compact tool calls within messages - s.Messages.ToolCallCompact = muted - s.Messages.SectionHeader = base.PaddingLeft(2) - s.Messages.AssistantInfoIcon = subtle - s.Messages.AssistantInfoModel = muted - s.Messages.AssistantInfoProvider = subtle - s.Messages.AssistantInfoDuration = subtle - s.Messages.AssistantCanceled = lipgloss.NewStyle().Foreground(fgBase).Italic(true) - - // Thinking section styles - s.Messages.ThinkingBox = subtle.Background(bgBaseLighter) - s.Messages.ThinkingTruncationHint = muted - s.Messages.ThinkingFooterTitle = muted - s.Messages.ThinkingFooterDuration = subtle - - // Text selection. - s.TextSelection = lipgloss.NewStyle().Foreground(charmtone.Salt).Background(charmtone.Charple) - - // Dialog styles - s.Dialog.Title = base.Padding(0, 1).Foreground(primary) - s.Dialog.TitleText = base.Foreground(primary) - s.Dialog.TitleError = base.Foreground(red) - s.Dialog.TitleAccent = base.Foreground(green).Bold(true) - s.Dialog.TitleLineBase = lipgloss.NewStyle() - s.Dialog.TitleGradFromColor = primary - s.Dialog.TitleGradToColor = secondary - - // Dialog.ListItem (commands, reasoning, models) - s.Dialog.ListItem.InfoBlurred = lipgloss.NewStyle().Foreground(fgBase) - s.Dialog.ListItem.InfoFocused = lipgloss.NewStyle().Foreground(fgBase) - - // Dialog.Models - s.Dialog.Models.ConfiguredText = lipgloss.NewStyle().Foreground(fgSubtle) - - // Dialog.Permissions - s.Dialog.Permissions.KeyText = lipgloss.NewStyle().Foreground(fgMuted) - s.Dialog.Permissions.ValueText = lipgloss.NewStyle().Foreground(fgBase) - s.Dialog.Permissions.ParamsBg = bgSubtle - - // Dialog.Quit - s.Dialog.Quit.Content = lipgloss.NewStyle().Foreground(fgBase) - s.Dialog.Quit.Frame = lipgloss.NewStyle().BorderForeground(borderFocus).Border(lipgloss.RoundedBorder()).Padding(1, 2) - s.Dialog.View = base.Border(lipgloss.RoundedBorder()).BorderForeground(borderFocus) - s.Dialog.PrimaryText = base.Padding(0, 1).Foreground(primary) - s.Dialog.SecondaryText = base.Padding(0, 1).Foreground(fgSubtle) - s.Dialog.HelpView = base.Padding(0, 1).AlignHorizontal(lipgloss.Left) - s.Dialog.Help.ShortKey = base.Foreground(fgMuted) - s.Dialog.Help.ShortDesc = base.Foreground(fgSubtle) - s.Dialog.Help.ShortSeparator = base.Foreground(border) - s.Dialog.Help.Ellipsis = base.Foreground(border) - s.Dialog.Help.FullKey = base.Foreground(fgMuted) - s.Dialog.Help.FullDesc = base.Foreground(fgSubtle) - s.Dialog.Help.FullSeparator = base.Foreground(border) - s.Dialog.NormalItem = base.Padding(0, 1).Foreground(fgBase) - s.Dialog.SelectedItem = base.Padding(0, 1).Background(primary).Foreground(fgBase) - s.Dialog.InputPrompt = base.Margin(1, 1) - - s.Dialog.List = base.Margin(0, 0, 1, 0) - s.Dialog.ContentPanel = base.Background(bgSubtle).Foreground(fgBase).Padding(1, 2) - s.Dialog.Spinner = base.Foreground(secondary) - s.Dialog.ScrollbarThumb = base.Foreground(secondary) - s.Dialog.ScrollbarTrack = base.Foreground(border) - - s.Dialog.ImagePreview = lipgloss.NewStyle().Padding(0, 1).Foreground(fgSubtle) - - // API key input dialog - s.Dialog.APIKey.Spinner = base.Foreground(green) - - // OAuth dialog - s.Dialog.OAuth.Spinner = base.Foreground(greenLight) - s.Dialog.OAuth.Instructions = lipgloss.NewStyle().Foreground(white) - s.Dialog.OAuth.UserCode = lipgloss.NewStyle().Bold(true).Foreground(white) - s.Dialog.OAuth.Success = lipgloss.NewStyle().Foreground(greenLight) - s.Dialog.OAuth.Link = lipgloss.NewStyle().Foreground(greenDark).Underline(true) - s.Dialog.OAuth.Enter = lipgloss.NewStyle().Foreground(primary) - s.Dialog.OAuth.ErrorText = lipgloss.NewStyle().Foreground(error) - s.Dialog.OAuth.StatusText = lipgloss.NewStyle().Foreground(fgMuted) - s.Dialog.OAuth.UserCodeBg = bgBaseLighter - - s.Dialog.Arguments.Content = base.Padding(1) - s.Dialog.Arguments.Description = base.MarginBottom(1).MaxHeight(3) - s.Dialog.Arguments.InputLabelBlurred = base.Foreground(fgMuted) - s.Dialog.Arguments.InputLabelFocused = base.Bold(true) - s.Dialog.Arguments.InputRequiredMarkBlurred = base.Foreground(fgMuted).SetString("*") - s.Dialog.Arguments.InputRequiredMarkFocused = base.Foreground(primary).Bold(true).SetString("*") - - s.Dialog.Sessions.DeletingTitle = s.Dialog.Title.Foreground(red) - s.Dialog.Sessions.DeletingView = s.Dialog.View.BorderForeground(red) - s.Dialog.Sessions.DeletingMessage = base.Padding(1) - s.Dialog.Sessions.DeletingTitleGradientFromColor = red - s.Dialog.Sessions.DeletingTitleGradientToColor = primary - s.Dialog.Sessions.DeletingItemBlurred = s.Dialog.NormalItem.Foreground(fgSubtle) - s.Dialog.Sessions.DeletingItemFocused = s.Dialog.SelectedItem.Background(red).Foreground(charmtone.Butter) - - s.Dialog.Sessions.RenamingingTitle = s.Dialog.Title.Foreground(charmtone.Zest) - s.Dialog.Sessions.RenamingView = s.Dialog.View.BorderForeground(charmtone.Zest) - s.Dialog.Sessions.RenamingingMessage = base.Padding(1) - s.Dialog.Sessions.RenamingTitleGradientFromColor = charmtone.Zest - s.Dialog.Sessions.RenamingTitleGradientToColor = charmtone.Bok - s.Dialog.Sessions.RenamingItemBlurred = s.Dialog.NormalItem.Foreground(fgSubtle) - s.Dialog.Sessions.RenamingingItemFocused = s.Dialog.SelectedItem.UnsetBackground().UnsetForeground() - s.Dialog.Sessions.RenamingPlaceholder = base.Foreground(charmtone.Squid) - s.Dialog.Sessions.InfoBlurred = lipgloss.NewStyle().Foreground(fgSubtle) - s.Dialog.Sessions.InfoFocused = lipgloss.NewStyle().Foreground(fgBase) - - s.Status.Help = lipgloss.NewStyle().Padding(0, 1) - s.Status.SuccessIndicator = base.Foreground(bgSubtle).Background(green).Padding(0, 1).Bold(true).SetString("OKAY!") - s.Status.InfoIndicator = s.Status.SuccessIndicator - s.Status.UpdateIndicator = s.Status.SuccessIndicator.SetString("HEY!") - s.Status.WarnIndicator = s.Status.SuccessIndicator.Foreground(bgOverlay).Background(yellow).SetString("WARNING") - s.Status.ErrorIndicator = s.Status.SuccessIndicator.Foreground(bgBase).Background(red).SetString("ERROR") - s.Status.SuccessMessage = base.Foreground(bgSubtle).Background(greenDark).Padding(0, 1) - s.Status.InfoMessage = s.Status.SuccessMessage - s.Status.UpdateMessage = s.Status.SuccessMessage - s.Status.WarnMessage = s.Status.SuccessMessage.Foreground(bgOverlay).Background(warning) - s.Status.ErrorMessage = s.Status.SuccessMessage.Foreground(white).Background(redDark) - - // Completions styles - s.Completions.Normal = base.Background(bgSubtle).Foreground(fgBase) - s.Completions.Focused = base.Background(primary).Foreground(white) - s.Completions.Match = base.Underline(true) - - // Attachments styles - attachmentIconStyle := base.Foreground(bgSubtle).Background(green).Padding(0, 1) - s.Attachments.Image = attachmentIconStyle.SetString(ImageIcon) - s.Attachments.Text = attachmentIconStyle.SetString(TextIcon) - s.Attachments.Normal = base.Padding(0, 1).MarginRight(1).Background(fgMuted).Foreground(fgBase) - s.Attachments.Deleting = base.Padding(0, 1).Bold(true).Background(red).Foreground(fgBase) - - // Pills styles - s.Pills.Base = base.Padding(0, 1) - s.Pills.Focused = base.Padding(0, 1).BorderStyle(lipgloss.RoundedBorder()).BorderForeground(bgOverlay) - s.Pills.Blurred = base.Padding(0, 1).BorderStyle(lipgloss.HiddenBorder()) - s.Pills.QueueItemPrefix = lipgloss.NewStyle().Foreground(fgMuted).SetString(" •") - s.Pills.QueueItemText = lipgloss.NewStyle().Foreground(fgMuted) - s.Pills.QueueLabel = lipgloss.NewStyle().Foreground(fgBase) - s.Pills.QueueIconBase = lipgloss.NewStyle().Foreground(fgBase) - s.Pills.QueueGradFromColor = redDark - s.Pills.QueueGradToColor = secondary - s.Pills.TodoLabel = lipgloss.NewStyle().Foreground(fgBase) - s.Pills.TodoProgress = lipgloss.NewStyle().Foreground(fgMuted) - s.Pills.TodoCurrentTask = lipgloss.NewStyle().Foreground(fgSubtle) - s.Pills.TodoSpinner = lipgloss.NewStyle().Foreground(greenDark) - s.Pills.HelpKey = lipgloss.NewStyle().Foreground(fgMuted) - s.Pills.HelpText = lipgloss.NewStyle().Foreground(fgSubtle) - s.Pills.Area = base - - return s +// hex returns a pointer to the "#rrggbb" representation of c. It's used to +// satisfy glamour's string-pointer API when configuring markdown colors +// from the theme palette. +func hex(c color.Color) *string { + r, g, b, _ := c.RGBA() + s := fmt.Sprintf("#%02x%02x%02x", r>>8, g>>8, b>>8) + return &s } func chromaStyle(style ansi.StylePrimitive) string { diff --git a/internal/ui/styles/themes.go b/internal/ui/styles/themes.go new file mode 100644 index 0000000000000000000000000000000000000000..39782c32840e44ebd4b134432be2d7df7fcd99a8 --- /dev/null +++ b/internal/ui/styles/themes.go @@ -0,0 +1,41 @@ +package styles + +import "github.com/charmbracelet/x/exp/charmtone" + +// CharmtonePantera returns the Charmtone dark theme. It's the default style +// for the UI. +func CharmtonePantera() Styles { + return quickStyle(quickStyleOpts{ + primary: charmtone.Charple, + secondary: charmtone.Dolly, + tertiary: charmtone.Bok, + + fgBase: charmtone.Ash, + fgMuted: charmtone.Squid, + fgHalfMuted: charmtone.Smoke, + fgSubtle: charmtone.Oyster, + + onPrimary: charmtone.Salt, + onAccent: charmtone.Butter, + + bgBase: charmtone.Pepper, + bgBaseLighter: charmtone.BBQ, + bgSubtle: charmtone.Charcoal, + bgOverlay: charmtone.Iron, + + border: charmtone.Charcoal, + borderFocus: charmtone.Charple, + + danger: charmtone.Coral, + error: charmtone.Sriracha, + warning: charmtone.Zest, + warningStrong: charmtone.Mustard, + busy: charmtone.Citron, + info: charmtone.Malibu, + infoSubtle: charmtone.Sardine, + infoMuted: charmtone.Damson, + success: charmtone.Julep, + successSubtle: charmtone.Bok, + successMuted: charmtone.Guac, + }) +}