styles.go

   1package styles
   2
   3import (
   4	"image/color"
   5
   6	"charm.land/bubbles/v2/filepicker"
   7	"charm.land/bubbles/v2/help"
   8	"charm.land/bubbles/v2/textarea"
   9	"charm.land/bubbles/v2/textinput"
  10	tea "charm.land/bubbletea/v2"
  11	"charm.land/glamour/v2/ansi"
  12	"charm.land/lipgloss/v2"
  13	"github.com/alecthomas/chroma/v2"
  14	"github.com/charmbracelet/crush/internal/tui/exp/diffview"
  15	"github.com/charmbracelet/x/exp/charmtone"
  16)
  17
  18const (
  19	CheckIcon    string = "✓"
  20	ErrorIcon    string = "×"
  21	WarningIcon  string = "⚠"
  22	InfoIcon     string = "ⓘ"
  23	HintIcon     string = "∵"
  24	SpinnerIcon  string = "..."
  25	LoadingIcon  string = "⟳"
  26	DocumentIcon string = "🖼"
  27	ModelIcon    string = "◇"
  28
  29	// Arrow icons
  30	ArrowRightIcon string = "→"
  31
  32	// Tool call icons
  33	ToolPending string = "●"
  34	ToolSuccess string = "✓"
  35	ToolError   string = "×"
  36
  37	// Border styles
  38	BorderThin  string = "│"
  39	BorderThick string = "▌"
  40
  41	// Section separator
  42	SectionSeparator string = "─"
  43
  44	// Todo icons
  45	TodoCompletedIcon  string = "✓"
  46	TodoPendingIcon    string = "•"
  47	TodoInProgressIcon string = "→"
  48)
  49
  50const (
  51	defaultMargin     = 2
  52	defaultListIndent = 2
  53)
  54
  55type Styles struct {
  56	WindowTooSmall lipgloss.Style
  57
  58	// Reusable text styles
  59	Base   lipgloss.Style
  60	Muted  lipgloss.Style
  61	Subtle lipgloss.Style
  62
  63	// Tags
  64	TagBase  lipgloss.Style
  65	TagError lipgloss.Style
  66	TagInfo  lipgloss.Style
  67
  68	// Headers
  69	HeaderTool       lipgloss.Style
  70	HeaderToolNested lipgloss.Style
  71
  72	// Panels
  73	PanelMuted lipgloss.Style
  74	PanelBase  lipgloss.Style
  75
  76	// Line numbers for code blocks
  77	LineNumber lipgloss.Style
  78
  79	// Message borders
  80	FocusedMessageBorder lipgloss.Border
  81
  82	// Tool calls
  83	ToolCallPending   lipgloss.Style
  84	ToolCallError     lipgloss.Style
  85	ToolCallSuccess   lipgloss.Style
  86	ToolCallCancelled lipgloss.Style
  87	EarlyStateMessage lipgloss.Style
  88
  89	// Text selection
  90	TextSelection lipgloss.Style
  91
  92	// LSP and MCP status indicators
  93	ItemOfflineIcon lipgloss.Style
  94	ItemBusyIcon    lipgloss.Style
  95	ItemErrorIcon   lipgloss.Style
  96	ItemOnlineIcon  lipgloss.Style
  97
  98	// Markdown & Chroma
  99	Markdown      ansi.StyleConfig
 100	PlainMarkdown ansi.StyleConfig
 101
 102	// Inputs
 103	TextInput textinput.Styles
 104	TextArea  textarea.Styles
 105
 106	// Help
 107	Help help.Styles
 108
 109	// Diff
 110	Diff diffview.Style
 111
 112	// FilePicker
 113	FilePicker filepicker.Styles
 114
 115	// Buttons
 116	ButtonFocus lipgloss.Style
 117	ButtonBlur  lipgloss.Style
 118
 119	// Borders
 120	BorderFocus lipgloss.Style
 121	BorderBlur  lipgloss.Style
 122
 123	// Editor
 124	EditorPromptNormalFocused   lipgloss.Style
 125	EditorPromptNormalBlurred   lipgloss.Style
 126	EditorPromptYoloIconFocused lipgloss.Style
 127	EditorPromptYoloIconBlurred lipgloss.Style
 128	EditorPromptYoloDotsFocused lipgloss.Style
 129	EditorPromptYoloDotsBlurred lipgloss.Style
 130
 131	// Background
 132	Background color.Color
 133
 134	// Logo
 135	LogoFieldColor   color.Color
 136	LogoTitleColorA  color.Color
 137	LogoTitleColorB  color.Color
 138	LogoCharmColor   color.Color
 139	LogoVersionColor color.Color
 140
 141	// Colors - semantic colors for tool rendering.
 142	Primary       color.Color
 143	Secondary     color.Color
 144	Tertiary      color.Color
 145	BgBase        color.Color
 146	BgBaseLighter color.Color
 147	BgSubtle      color.Color
 148	BgOverlay     color.Color
 149	FgBase        color.Color
 150	FgMuted       color.Color
 151	FgHalfMuted   color.Color
 152	FgSubtle      color.Color
 153	Border        color.Color
 154	BorderColor   color.Color // Border focus color
 155	Warning       color.Color
 156	Info          color.Color
 157	White         color.Color
 158	BlueLight     color.Color
 159	Blue          color.Color
 160	Green         color.Color
 161	GreenDark     color.Color
 162	Red           color.Color
 163	RedDark       color.Color
 164	Yellow        color.Color
 165
 166	// Section Title
 167	Section struct {
 168		Title lipgloss.Style
 169		Line  lipgloss.Style
 170	}
 171
 172	// Initialize
 173	Initialize struct {
 174		Header  lipgloss.Style
 175		Content lipgloss.Style
 176		Accent  lipgloss.Style
 177	}
 178
 179	// LSP
 180	LSP struct {
 181		ErrorDiagnostic   lipgloss.Style
 182		WarningDiagnostic lipgloss.Style
 183		HintDiagnostic    lipgloss.Style
 184		InfoDiagnostic    lipgloss.Style
 185	}
 186
 187	// Files
 188	Files struct {
 189		Path      lipgloss.Style
 190		Additions lipgloss.Style
 191		Deletions lipgloss.Style
 192	}
 193
 194	// Chat
 195	Chat struct {
 196		// Message item styles
 197		Message struct {
 198			UserBlurred      lipgloss.Style
 199			UserFocused      lipgloss.Style
 200			AssistantBlurred lipgloss.Style
 201			AssistantFocused lipgloss.Style
 202			NoContent        lipgloss.Style
 203			Thinking         lipgloss.Style
 204			ErrorTag         lipgloss.Style
 205			ErrorTitle       lipgloss.Style
 206			ErrorDetails     lipgloss.Style
 207			Attachment       lipgloss.Style
 208			ToolCallFocused  lipgloss.Style
 209			ToolCallBlurred  lipgloss.Style
 210			SectionHeader    lipgloss.Style
 211
 212			// Section styles - for assistant response metadata
 213			SectionIcon     lipgloss.Style // Model icon
 214			SectionModel    lipgloss.Style // Model name
 215			SectionDuration lipgloss.Style // Response duration
 216
 217			// Thinking section styles
 218			ThinkingBox             lipgloss.Style // Background for thinking content
 219			ThinkingTruncationHint  lipgloss.Style // "… (N lines hidden)" hint
 220			ThinkingFooterTitle     lipgloss.Style // "Thought for" text
 221			ThinkingFooterDuration  lipgloss.Style // Duration value
 222			ThinkingFooterCancelled lipgloss.Style // "*Canceled*" text
 223		}
 224	}
 225
 226	// Tool - styles for tool call rendering
 227	Tool struct {
 228		// Icon styles with tool status
 229		IconPending   lipgloss.Style // Pending operation icon
 230		IconSuccess   lipgloss.Style // Successful operation icon
 231		IconError     lipgloss.Style // Error operation icon
 232		IconCancelled lipgloss.Style // Cancelled operation icon
 233
 234		// Tool name styles
 235		NameNormal lipgloss.Style // Normal tool name
 236		NameNested lipgloss.Style // Nested tool name
 237
 238		// Parameter list styles
 239		ParamMain lipgloss.Style // Main parameter
 240		ParamKey  lipgloss.Style // Parameter keys
 241
 242		// Content rendering styles
 243		ContentLine       lipgloss.Style // Individual content line with background and width
 244		ContentTruncation lipgloss.Style // Truncation message "… (N lines)"
 245		ContentCodeLine   lipgloss.Style // Code line with background and width
 246		ContentCodeBg     color.Color    // Background color for syntax highlighting
 247		BodyPadding       lipgloss.Style // Body content padding (PaddingLeft(2))
 248
 249		// Deprecated - kept for backward compatibility
 250		ContentBg         lipgloss.Style // Content background
 251		ContentText       lipgloss.Style // Content text
 252		ContentLineNumber lipgloss.Style // Line numbers in code
 253
 254		// State message styles
 255		StateWaiting   lipgloss.Style // "Waiting for tool response..."
 256		StateCancelled lipgloss.Style // "Canceled."
 257
 258		// Error styles
 259		ErrorTag     lipgloss.Style // ERROR tag
 260		ErrorMessage lipgloss.Style // Error message text
 261
 262		// Diff styles
 263		DiffTruncation lipgloss.Style // Diff truncation message with padding
 264
 265		// Multi-edit note styles
 266		NoteTag     lipgloss.Style // NOTE tag (yellow background)
 267		NoteMessage lipgloss.Style // Note message text
 268
 269		// Job header styles (for bash jobs)
 270		JobIconPending lipgloss.Style // Pending job icon (green dark)
 271		JobIconError   lipgloss.Style // Error job icon (red dark)
 272		JobIconSuccess lipgloss.Style // Success job icon (green)
 273		JobToolName    lipgloss.Style // Job tool name "Bash" (blue)
 274		JobAction      lipgloss.Style // Action text (Start, Output, Kill)
 275		JobPID         lipgloss.Style // PID text
 276		JobDescription lipgloss.Style // Description text
 277
 278		// Agent task styles
 279		AgentTaskTag lipgloss.Style // Agent task tag (blue background, bold)
 280		AgentPrompt  lipgloss.Style // Agent prompt text
 281	}
 282
 283	// Dialog styles
 284	Dialog struct {
 285		Title lipgloss.Style
 286		// View is the main content area style.
 287		View lipgloss.Style
 288		// HelpView is the line that contains the help.
 289		HelpView lipgloss.Style
 290		Help     struct {
 291			Ellipsis       lipgloss.Style
 292			ShortKey       lipgloss.Style
 293			ShortDesc      lipgloss.Style
 294			ShortSeparator lipgloss.Style
 295			FullKey        lipgloss.Style
 296			FullDesc       lipgloss.Style
 297			FullSeparator  lipgloss.Style
 298		}
 299		NormalItem   lipgloss.Style
 300		SelectedItem lipgloss.Style
 301		InputPrompt  lipgloss.Style
 302	}
 303}
 304
 305// ChromaTheme converts the current markdown chroma styles to a chroma
 306// StyleEntries map.
 307func (s *Styles) ChromaTheme() chroma.StyleEntries {
 308	rules := s.Markdown.CodeBlock
 309
 310	return chroma.StyleEntries{
 311		chroma.Text:                chromaStyle(rules.Chroma.Text),
 312		chroma.Error:               chromaStyle(rules.Chroma.Error),
 313		chroma.Comment:             chromaStyle(rules.Chroma.Comment),
 314		chroma.CommentPreproc:      chromaStyle(rules.Chroma.CommentPreproc),
 315		chroma.Keyword:             chromaStyle(rules.Chroma.Keyword),
 316		chroma.KeywordReserved:     chromaStyle(rules.Chroma.KeywordReserved),
 317		chroma.KeywordNamespace:    chromaStyle(rules.Chroma.KeywordNamespace),
 318		chroma.KeywordType:         chromaStyle(rules.Chroma.KeywordType),
 319		chroma.Operator:            chromaStyle(rules.Chroma.Operator),
 320		chroma.Punctuation:         chromaStyle(rules.Chroma.Punctuation),
 321		chroma.Name:                chromaStyle(rules.Chroma.Name),
 322		chroma.NameBuiltin:         chromaStyle(rules.Chroma.NameBuiltin),
 323		chroma.NameTag:             chromaStyle(rules.Chroma.NameTag),
 324		chroma.NameAttribute:       chromaStyle(rules.Chroma.NameAttribute),
 325		chroma.NameClass:           chromaStyle(rules.Chroma.NameClass),
 326		chroma.NameConstant:        chromaStyle(rules.Chroma.NameConstant),
 327		chroma.NameDecorator:       chromaStyle(rules.Chroma.NameDecorator),
 328		chroma.NameException:       chromaStyle(rules.Chroma.NameException),
 329		chroma.NameFunction:        chromaStyle(rules.Chroma.NameFunction),
 330		chroma.NameOther:           chromaStyle(rules.Chroma.NameOther),
 331		chroma.Literal:             chromaStyle(rules.Chroma.Literal),
 332		chroma.LiteralNumber:       chromaStyle(rules.Chroma.LiteralNumber),
 333		chroma.LiteralDate:         chromaStyle(rules.Chroma.LiteralDate),
 334		chroma.LiteralString:       chromaStyle(rules.Chroma.LiteralString),
 335		chroma.LiteralStringEscape: chromaStyle(rules.Chroma.LiteralStringEscape),
 336		chroma.GenericDeleted:      chromaStyle(rules.Chroma.GenericDeleted),
 337		chroma.GenericEmph:         chromaStyle(rules.Chroma.GenericEmph),
 338		chroma.GenericInserted:     chromaStyle(rules.Chroma.GenericInserted),
 339		chroma.GenericStrong:       chromaStyle(rules.Chroma.GenericStrong),
 340		chroma.GenericSubheading:   chromaStyle(rules.Chroma.GenericSubheading),
 341		chroma.Background:          chromaStyle(rules.Chroma.Background),
 342	}
 343}
 344
 345// DialogHelpStyles returns the styles for dialog help.
 346func (s *Styles) DialogHelpStyles() help.Styles {
 347	return help.Styles(s.Dialog.Help)
 348}
 349
 350// DefaultStyles returns the default styles for the UI.
 351func DefaultStyles() Styles {
 352	var (
 353		primary   = charmtone.Charple
 354		secondary = charmtone.Dolly
 355		tertiary  = charmtone.Bok
 356		// accent    = charmtone.Zest
 357
 358		// Backgrounds
 359		bgBase        = charmtone.Pepper
 360		bgBaseLighter = charmtone.BBQ
 361		bgSubtle      = charmtone.Charcoal
 362		bgOverlay     = charmtone.Iron
 363
 364		// Foregrounds
 365		fgBase      = charmtone.Ash
 366		fgMuted     = charmtone.Squid
 367		fgHalfMuted = charmtone.Smoke
 368		fgSubtle    = charmtone.Oyster
 369		// fgSelected  = charmtone.Salt
 370
 371		// Borders
 372		border      = charmtone.Charcoal
 373		borderFocus = charmtone.Charple
 374
 375		// Status
 376		warning = charmtone.Zest
 377		info    = charmtone.Malibu
 378
 379		// Colors
 380		white = charmtone.Butter
 381
 382		blueLight = charmtone.Sardine
 383		blue      = charmtone.Malibu
 384
 385		// yellow = charmtone.Mustard
 386		yellow = charmtone.Mustard
 387		// citron = charmtone.Citron
 388
 389		green     = charmtone.Julep
 390		greenDark = charmtone.Guac
 391		// greenLight = charmtone.Bok
 392
 393		red     = charmtone.Coral
 394		redDark = charmtone.Sriracha
 395		// redLight = charmtone.Salmon
 396		// cherry   = charmtone.Cherry
 397	)
 398
 399	normalBorder := lipgloss.NormalBorder()
 400
 401	base := lipgloss.NewStyle().Foreground(fgBase)
 402
 403	s := Styles{}
 404
 405	s.Background = bgBase
 406
 407	// Populate color fields
 408	s.Primary = primary
 409	s.Secondary = secondary
 410	s.Tertiary = tertiary
 411	s.BgBase = bgBase
 412	s.BgBaseLighter = bgBaseLighter
 413	s.BgSubtle = bgSubtle
 414	s.BgOverlay = bgOverlay
 415	s.FgBase = fgBase
 416	s.FgMuted = fgMuted
 417	s.FgHalfMuted = fgHalfMuted
 418	s.FgSubtle = fgSubtle
 419	s.Border = border
 420	s.BorderColor = borderFocus
 421	s.Warning = warning
 422	s.Info = info
 423	s.White = white
 424	s.BlueLight = blueLight
 425	s.Blue = blue
 426	s.Green = green
 427	s.GreenDark = greenDark
 428	s.Red = red
 429	s.RedDark = redDark
 430	s.Yellow = yellow
 431
 432	s.TextInput = textinput.Styles{
 433		Focused: textinput.StyleState{
 434			Text:        base,
 435			Placeholder: base.Foreground(fgSubtle),
 436			Prompt:      base.Foreground(tertiary),
 437			Suggestion:  base.Foreground(fgSubtle),
 438		},
 439		Blurred: textinput.StyleState{
 440			Text:        base.Foreground(fgMuted),
 441			Placeholder: base.Foreground(fgSubtle),
 442			Prompt:      base.Foreground(fgMuted),
 443			Suggestion:  base.Foreground(fgSubtle),
 444		},
 445		Cursor: textinput.CursorStyle{
 446			Color: secondary,
 447			Shape: tea.CursorBlock,
 448			Blink: true,
 449		},
 450	}
 451
 452	s.TextArea = textarea.Styles{
 453		Focused: textarea.StyleState{
 454			Base:             base,
 455			Text:             base,
 456			LineNumber:       base.Foreground(fgSubtle),
 457			CursorLine:       base,
 458			CursorLineNumber: base.Foreground(fgSubtle),
 459			Placeholder:      base.Foreground(fgSubtle),
 460			Prompt:           base.Foreground(tertiary),
 461		},
 462		Blurred: textarea.StyleState{
 463			Base:             base,
 464			Text:             base.Foreground(fgMuted),
 465			LineNumber:       base.Foreground(fgMuted),
 466			CursorLine:       base,
 467			CursorLineNumber: base.Foreground(fgMuted),
 468			Placeholder:      base.Foreground(fgSubtle),
 469			Prompt:           base.Foreground(fgMuted),
 470		},
 471		Cursor: textarea.CursorStyle{
 472			Color: secondary,
 473			Shape: tea.CursorBlock,
 474			Blink: true,
 475		},
 476	}
 477
 478	s.Markdown = ansi.StyleConfig{
 479		Document: ansi.StyleBlock{
 480			StylePrimitive: ansi.StylePrimitive{
 481				// BlockPrefix: "\n",
 482				// BlockSuffix: "\n",
 483				Color: stringPtr(charmtone.Smoke.Hex()),
 484			},
 485			// Margin: uintPtr(defaultMargin),
 486		},
 487		BlockQuote: ansi.StyleBlock{
 488			StylePrimitive: ansi.StylePrimitive{},
 489			Indent:         uintPtr(1),
 490			IndentToken:    stringPtr("│ "),
 491		},
 492		List: ansi.StyleList{
 493			LevelIndent: defaultListIndent,
 494		},
 495		Heading: ansi.StyleBlock{
 496			StylePrimitive: ansi.StylePrimitive{
 497				BlockSuffix: "\n",
 498				Color:       stringPtr(charmtone.Malibu.Hex()),
 499				Bold:        boolPtr(true),
 500			},
 501		},
 502		H1: ansi.StyleBlock{
 503			StylePrimitive: ansi.StylePrimitive{
 504				Prefix:          " ",
 505				Suffix:          " ",
 506				Color:           stringPtr(charmtone.Zest.Hex()),
 507				BackgroundColor: stringPtr(charmtone.Charple.Hex()),
 508				Bold:            boolPtr(true),
 509			},
 510		},
 511		H2: ansi.StyleBlock{
 512			StylePrimitive: ansi.StylePrimitive{
 513				Prefix: "## ",
 514			},
 515		},
 516		H3: ansi.StyleBlock{
 517			StylePrimitive: ansi.StylePrimitive{
 518				Prefix: "### ",
 519			},
 520		},
 521		H4: ansi.StyleBlock{
 522			StylePrimitive: ansi.StylePrimitive{
 523				Prefix: "#### ",
 524			},
 525		},
 526		H5: ansi.StyleBlock{
 527			StylePrimitive: ansi.StylePrimitive{
 528				Prefix: "##### ",
 529			},
 530		},
 531		H6: ansi.StyleBlock{
 532			StylePrimitive: ansi.StylePrimitive{
 533				Prefix: "###### ",
 534				Color:  stringPtr(charmtone.Guac.Hex()),
 535				Bold:   boolPtr(false),
 536			},
 537		},
 538		Strikethrough: ansi.StylePrimitive{
 539			CrossedOut: boolPtr(true),
 540		},
 541		Emph: ansi.StylePrimitive{
 542			Italic: boolPtr(true),
 543		},
 544		Strong: ansi.StylePrimitive{
 545			Bold: boolPtr(true),
 546		},
 547		HorizontalRule: ansi.StylePrimitive{
 548			Color:  stringPtr(charmtone.Charcoal.Hex()),
 549			Format: "\n--------\n",
 550		},
 551		Item: ansi.StylePrimitive{
 552			BlockPrefix: "• ",
 553		},
 554		Enumeration: ansi.StylePrimitive{
 555			BlockPrefix: ". ",
 556		},
 557		Task: ansi.StyleTask{
 558			StylePrimitive: ansi.StylePrimitive{},
 559			Ticked:         "[✓] ",
 560			Unticked:       "[ ] ",
 561		},
 562		Link: ansi.StylePrimitive{
 563			Color:     stringPtr(charmtone.Zinc.Hex()),
 564			Underline: boolPtr(true),
 565		},
 566		LinkText: ansi.StylePrimitive{
 567			Color: stringPtr(charmtone.Guac.Hex()),
 568			Bold:  boolPtr(true),
 569		},
 570		Image: ansi.StylePrimitive{
 571			Color:     stringPtr(charmtone.Cheeky.Hex()),
 572			Underline: boolPtr(true),
 573		},
 574		ImageText: ansi.StylePrimitive{
 575			Color:  stringPtr(charmtone.Squid.Hex()),
 576			Format: "Image: {{.text}} →",
 577		},
 578		Code: ansi.StyleBlock{
 579			StylePrimitive: ansi.StylePrimitive{
 580				Prefix:          " ",
 581				Suffix:          " ",
 582				Color:           stringPtr(charmtone.Coral.Hex()),
 583				BackgroundColor: stringPtr(charmtone.Charcoal.Hex()),
 584			},
 585		},
 586		CodeBlock: ansi.StyleCodeBlock{
 587			StyleBlock: ansi.StyleBlock{
 588				StylePrimitive: ansi.StylePrimitive{
 589					Color: stringPtr(charmtone.Charcoal.Hex()),
 590				},
 591				Margin: uintPtr(defaultMargin),
 592			},
 593			Chroma: &ansi.Chroma{
 594				Text: ansi.StylePrimitive{
 595					Color: stringPtr(charmtone.Smoke.Hex()),
 596				},
 597				Error: ansi.StylePrimitive{
 598					Color:           stringPtr(charmtone.Butter.Hex()),
 599					BackgroundColor: stringPtr(charmtone.Sriracha.Hex()),
 600				},
 601				Comment: ansi.StylePrimitive{
 602					Color: stringPtr(charmtone.Oyster.Hex()),
 603				},
 604				CommentPreproc: ansi.StylePrimitive{
 605					Color: stringPtr(charmtone.Bengal.Hex()),
 606				},
 607				Keyword: ansi.StylePrimitive{
 608					Color: stringPtr(charmtone.Malibu.Hex()),
 609				},
 610				KeywordReserved: ansi.StylePrimitive{
 611					Color: stringPtr(charmtone.Pony.Hex()),
 612				},
 613				KeywordNamespace: ansi.StylePrimitive{
 614					Color: stringPtr(charmtone.Pony.Hex()),
 615				},
 616				KeywordType: ansi.StylePrimitive{
 617					Color: stringPtr(charmtone.Guppy.Hex()),
 618				},
 619				Operator: ansi.StylePrimitive{
 620					Color: stringPtr(charmtone.Salmon.Hex()),
 621				},
 622				Punctuation: ansi.StylePrimitive{
 623					Color: stringPtr(charmtone.Zest.Hex()),
 624				},
 625				Name: ansi.StylePrimitive{
 626					Color: stringPtr(charmtone.Smoke.Hex()),
 627				},
 628				NameBuiltin: ansi.StylePrimitive{
 629					Color: stringPtr(charmtone.Cheeky.Hex()),
 630				},
 631				NameTag: ansi.StylePrimitive{
 632					Color: stringPtr(charmtone.Mauve.Hex()),
 633				},
 634				NameAttribute: ansi.StylePrimitive{
 635					Color: stringPtr(charmtone.Hazy.Hex()),
 636				},
 637				NameClass: ansi.StylePrimitive{
 638					Color:     stringPtr(charmtone.Salt.Hex()),
 639					Underline: boolPtr(true),
 640					Bold:      boolPtr(true),
 641				},
 642				NameDecorator: ansi.StylePrimitive{
 643					Color: stringPtr(charmtone.Citron.Hex()),
 644				},
 645				NameFunction: ansi.StylePrimitive{
 646					Color: stringPtr(charmtone.Guac.Hex()),
 647				},
 648				LiteralNumber: ansi.StylePrimitive{
 649					Color: stringPtr(charmtone.Julep.Hex()),
 650				},
 651				LiteralString: ansi.StylePrimitive{
 652					Color: stringPtr(charmtone.Cumin.Hex()),
 653				},
 654				LiteralStringEscape: ansi.StylePrimitive{
 655					Color: stringPtr(charmtone.Bok.Hex()),
 656				},
 657				GenericDeleted: ansi.StylePrimitive{
 658					Color: stringPtr(charmtone.Coral.Hex()),
 659				},
 660				GenericEmph: ansi.StylePrimitive{
 661					Italic: boolPtr(true),
 662				},
 663				GenericInserted: ansi.StylePrimitive{
 664					Color: stringPtr(charmtone.Guac.Hex()),
 665				},
 666				GenericStrong: ansi.StylePrimitive{
 667					Bold: boolPtr(true),
 668				},
 669				GenericSubheading: ansi.StylePrimitive{
 670					Color: stringPtr(charmtone.Squid.Hex()),
 671				},
 672				Background: ansi.StylePrimitive{
 673					BackgroundColor: stringPtr(charmtone.Charcoal.Hex()),
 674				},
 675			},
 676		},
 677		Table: ansi.StyleTable{
 678			StyleBlock: ansi.StyleBlock{
 679				StylePrimitive: ansi.StylePrimitive{},
 680			},
 681		},
 682		DefinitionDescription: ansi.StylePrimitive{
 683			BlockPrefix: "\n ",
 684		},
 685	}
 686
 687	// PlainMarkdown style - muted colors on subtle background for thinking content.
 688	plainBg := stringPtr(bgBaseLighter.Hex())
 689	plainFg := stringPtr(fgMuted.Hex())
 690	s.PlainMarkdown = ansi.StyleConfig{
 691		Document: ansi.StyleBlock{
 692			StylePrimitive: ansi.StylePrimitive{
 693				Color:           plainFg,
 694				BackgroundColor: plainBg,
 695			},
 696		},
 697		BlockQuote: ansi.StyleBlock{
 698			StylePrimitive: ansi.StylePrimitive{
 699				Color:           plainFg,
 700				BackgroundColor: plainBg,
 701			},
 702			Indent:      uintPtr(1),
 703			IndentToken: stringPtr("│ "),
 704		},
 705		List: ansi.StyleList{
 706			LevelIndent: defaultListIndent,
 707		},
 708		Heading: ansi.StyleBlock{
 709			StylePrimitive: ansi.StylePrimitive{
 710				BlockSuffix:     "\n",
 711				Bold:            boolPtr(true),
 712				Color:           plainFg,
 713				BackgroundColor: plainBg,
 714			},
 715		},
 716		H1: ansi.StyleBlock{
 717			StylePrimitive: ansi.StylePrimitive{
 718				Prefix:          " ",
 719				Suffix:          " ",
 720				Bold:            boolPtr(true),
 721				Color:           plainFg,
 722				BackgroundColor: plainBg,
 723			},
 724		},
 725		H2: ansi.StyleBlock{
 726			StylePrimitive: ansi.StylePrimitive{
 727				Prefix:          "## ",
 728				Color:           plainFg,
 729				BackgroundColor: plainBg,
 730			},
 731		},
 732		H3: ansi.StyleBlock{
 733			StylePrimitive: ansi.StylePrimitive{
 734				Prefix:          "### ",
 735				Color:           plainFg,
 736				BackgroundColor: plainBg,
 737			},
 738		},
 739		H4: ansi.StyleBlock{
 740			StylePrimitive: ansi.StylePrimitive{
 741				Prefix:          "#### ",
 742				Color:           plainFg,
 743				BackgroundColor: plainBg,
 744			},
 745		},
 746		H5: ansi.StyleBlock{
 747			StylePrimitive: ansi.StylePrimitive{
 748				Prefix:          "##### ",
 749				Color:           plainFg,
 750				BackgroundColor: plainBg,
 751			},
 752		},
 753		H6: ansi.StyleBlock{
 754			StylePrimitive: ansi.StylePrimitive{
 755				Prefix:          "###### ",
 756				Color:           plainFg,
 757				BackgroundColor: plainBg,
 758			},
 759		},
 760		Strikethrough: ansi.StylePrimitive{
 761			CrossedOut:      boolPtr(true),
 762			Color:           plainFg,
 763			BackgroundColor: plainBg,
 764		},
 765		Emph: ansi.StylePrimitive{
 766			Italic:          boolPtr(true),
 767			Color:           plainFg,
 768			BackgroundColor: plainBg,
 769		},
 770		Strong: ansi.StylePrimitive{
 771			Bold:            boolPtr(true),
 772			Color:           plainFg,
 773			BackgroundColor: plainBg,
 774		},
 775		HorizontalRule: ansi.StylePrimitive{
 776			Format:          "\n--------\n",
 777			Color:           plainFg,
 778			BackgroundColor: plainBg,
 779		},
 780		Item: ansi.StylePrimitive{
 781			BlockPrefix:     "• ",
 782			Color:           plainFg,
 783			BackgroundColor: plainBg,
 784		},
 785		Enumeration: ansi.StylePrimitive{
 786			BlockPrefix:     ". ",
 787			Color:           plainFg,
 788			BackgroundColor: plainBg,
 789		},
 790		Task: ansi.StyleTask{
 791			StylePrimitive: ansi.StylePrimitive{
 792				Color:           plainFg,
 793				BackgroundColor: plainBg,
 794			},
 795			Ticked:   "[✓] ",
 796			Unticked: "[ ] ",
 797		},
 798		Link: ansi.StylePrimitive{
 799			Underline:       boolPtr(true),
 800			Color:           plainFg,
 801			BackgroundColor: plainBg,
 802		},
 803		LinkText: ansi.StylePrimitive{
 804			Bold:            boolPtr(true),
 805			Color:           plainFg,
 806			BackgroundColor: plainBg,
 807		},
 808		Image: ansi.StylePrimitive{
 809			Underline:       boolPtr(true),
 810			Color:           plainFg,
 811			BackgroundColor: plainBg,
 812		},
 813		ImageText: ansi.StylePrimitive{
 814			Format:          "Image: {{.text}} →",
 815			Color:           plainFg,
 816			BackgroundColor: plainBg,
 817		},
 818		Code: ansi.StyleBlock{
 819			StylePrimitive: ansi.StylePrimitive{
 820				Prefix:          " ",
 821				Suffix:          " ",
 822				Color:           plainFg,
 823				BackgroundColor: plainBg,
 824			},
 825		},
 826		CodeBlock: ansi.StyleCodeBlock{
 827			StyleBlock: ansi.StyleBlock{
 828				StylePrimitive: ansi.StylePrimitive{
 829					Color:           plainFg,
 830					BackgroundColor: plainBg,
 831				},
 832				Margin: uintPtr(defaultMargin),
 833			},
 834		},
 835		Table: ansi.StyleTable{
 836			StyleBlock: ansi.StyleBlock{
 837				StylePrimitive: ansi.StylePrimitive{
 838					Color:           plainFg,
 839					BackgroundColor: plainBg,
 840				},
 841			},
 842		},
 843		DefinitionDescription: ansi.StylePrimitive{
 844			BlockPrefix:     "\n ",
 845			Color:           plainFg,
 846			BackgroundColor: plainBg,
 847		},
 848	}
 849
 850	s.Help = help.Styles{
 851		ShortKey:       base.Foreground(fgMuted),
 852		ShortDesc:      base.Foreground(fgSubtle),
 853		ShortSeparator: base.Foreground(border),
 854		Ellipsis:       base.Foreground(border),
 855		FullKey:        base.Foreground(fgMuted),
 856		FullDesc:       base.Foreground(fgSubtle),
 857		FullSeparator:  base.Foreground(border),
 858	}
 859
 860	s.Diff = diffview.Style{
 861		DividerLine: diffview.LineStyle{
 862			LineNumber: lipgloss.NewStyle().
 863				Foreground(fgHalfMuted).
 864				Background(bgBaseLighter),
 865			Code: lipgloss.NewStyle().
 866				Foreground(fgHalfMuted).
 867				Background(bgBaseLighter),
 868		},
 869		MissingLine: diffview.LineStyle{
 870			LineNumber: lipgloss.NewStyle().
 871				Background(bgBaseLighter),
 872			Code: lipgloss.NewStyle().
 873				Background(bgBaseLighter),
 874		},
 875		EqualLine: diffview.LineStyle{
 876			LineNumber: lipgloss.NewStyle().
 877				Foreground(fgMuted).
 878				Background(bgBase),
 879			Code: lipgloss.NewStyle().
 880				Foreground(fgMuted).
 881				Background(bgBase),
 882		},
 883		InsertLine: diffview.LineStyle{
 884			LineNumber: lipgloss.NewStyle().
 885				Foreground(lipgloss.Color("#629657")).
 886				Background(lipgloss.Color("#2b322a")),
 887			Symbol: lipgloss.NewStyle().
 888				Foreground(lipgloss.Color("#629657")).
 889				Background(lipgloss.Color("#323931")),
 890			Code: lipgloss.NewStyle().
 891				Background(lipgloss.Color("#323931")),
 892		},
 893		DeleteLine: diffview.LineStyle{
 894			LineNumber: lipgloss.NewStyle().
 895				Foreground(lipgloss.Color("#a45c59")).
 896				Background(lipgloss.Color("#312929")),
 897			Symbol: lipgloss.NewStyle().
 898				Foreground(lipgloss.Color("#a45c59")).
 899				Background(lipgloss.Color("#383030")),
 900			Code: lipgloss.NewStyle().
 901				Background(lipgloss.Color("#383030")),
 902		},
 903	}
 904
 905	s.FilePicker = filepicker.Styles{
 906		DisabledCursor:   base.Foreground(fgMuted),
 907		Cursor:           base.Foreground(fgBase),
 908		Symlink:          base.Foreground(fgSubtle),
 909		Directory:        base.Foreground(primary),
 910		File:             base.Foreground(fgBase),
 911		DisabledFile:     base.Foreground(fgMuted),
 912		DisabledSelected: base.Background(bgOverlay).Foreground(fgMuted),
 913		Permission:       base.Foreground(fgMuted),
 914		Selected:         base.Background(primary).Foreground(fgBase),
 915		FileSize:         base.Foreground(fgMuted),
 916		EmptyDirectory:   base.Foreground(fgMuted).PaddingLeft(2).SetString("Empty directory"),
 917	}
 918
 919	// borders
 920	s.FocusedMessageBorder = lipgloss.Border{Left: BorderThick}
 921
 922	// text presets
 923	s.Base = lipgloss.NewStyle().Foreground(fgBase)
 924	s.Muted = lipgloss.NewStyle().Foreground(fgMuted)
 925	s.Subtle = lipgloss.NewStyle().Foreground(fgSubtle)
 926
 927	s.WindowTooSmall = s.Muted
 928
 929	// tag presets
 930	s.TagBase = lipgloss.NewStyle().Padding(0, 1).Foreground(white)
 931	s.TagError = s.TagBase.Background(redDark)
 932	s.TagInfo = s.TagBase.Background(blueLight)
 933
 934	// headers
 935	s.HeaderTool = lipgloss.NewStyle().Foreground(blue)
 936	s.HeaderToolNested = lipgloss.NewStyle().Foreground(fgHalfMuted)
 937
 938	// panels
 939	s.PanelMuted = s.Muted.Background(bgBaseLighter)
 940	s.PanelBase = lipgloss.NewStyle().Background(bgBase)
 941
 942	// code line number
 943	s.LineNumber = lipgloss.NewStyle().Foreground(fgMuted).Background(bgBase).PaddingRight(1).PaddingLeft(1)
 944
 945	// Tool calls
 946	s.ToolCallPending = lipgloss.NewStyle().Foreground(greenDark).SetString(ToolPending)
 947	s.ToolCallError = lipgloss.NewStyle().Foreground(redDark).SetString(ToolError)
 948	s.ToolCallSuccess = lipgloss.NewStyle().Foreground(green).SetString(ToolSuccess)
 949	// Cancelled uses muted tone but same glyph as pending
 950	s.ToolCallCancelled = s.Muted.SetString(ToolPending)
 951	s.EarlyStateMessage = s.Subtle.PaddingLeft(2)
 952
 953	// Tool rendering styles
 954	s.Tool.IconPending = base.Foreground(greenDark).SetString(ToolPending)
 955	s.Tool.IconSuccess = base.Foreground(green).SetString(ToolSuccess)
 956	s.Tool.IconError = base.Foreground(redDark).SetString(ToolError)
 957	s.Tool.IconCancelled = s.Muted.SetString(ToolPending)
 958
 959	s.Tool.NameNormal = base.Foreground(blue)
 960	s.Tool.NameNested = base.Foreground(fgHalfMuted)
 961
 962	s.Tool.ParamMain = s.Subtle
 963	s.Tool.ParamKey = s.Subtle
 964
 965	// Content rendering - prepared styles that accept width parameter
 966	s.Tool.ContentLine = s.Muted.Background(bgBaseLighter)
 967	s.Tool.ContentTruncation = s.Muted.Background(bgBaseLighter)
 968	s.Tool.ContentCodeLine = s.Base.Background(bgBase)
 969	s.Tool.ContentCodeBg = bgBase
 970	s.Tool.BodyPadding = base.PaddingLeft(2)
 971
 972	// Deprecated - kept for backward compatibility
 973	s.Tool.ContentBg = s.Muted.Background(bgBaseLighter)
 974	s.Tool.ContentText = s.Muted
 975	s.Tool.ContentLineNumber = s.Subtle
 976
 977	s.Tool.StateWaiting = base.Foreground(fgSubtle)
 978	s.Tool.StateCancelled = base.Foreground(fgSubtle)
 979
 980	s.Tool.ErrorTag = base.Padding(0, 1).Background(red).Foreground(white)
 981	s.Tool.ErrorMessage = base.Foreground(fgHalfMuted)
 982
 983	// Diff and multi-edit styles
 984	s.Tool.DiffTruncation = s.Muted.Background(bgBaseLighter).PaddingLeft(2)
 985	s.Tool.NoteTag = base.Padding(0, 2).Background(info).Foreground(white)
 986	s.Tool.NoteMessage = base.Foreground(fgHalfMuted)
 987
 988	// Job header styles
 989	s.Tool.JobIconPending = base.Foreground(greenDark)
 990	s.Tool.JobIconError = base.Foreground(redDark)
 991	s.Tool.JobIconSuccess = base.Foreground(green)
 992	s.Tool.JobToolName = base.Foreground(blue)
 993	s.Tool.JobAction = base.Foreground(fgHalfMuted)
 994	s.Tool.JobPID = s.Subtle
 995	s.Tool.JobDescription = s.Subtle
 996
 997	// Agent task styles
 998	s.Tool.AgentTaskTag = base.Bold(true).Padding(0, 1).MarginLeft(2).Background(blueLight).Foreground(white)
 999	s.Tool.AgentPrompt = s.Muted
1000
1001	// Buttons
1002	s.ButtonFocus = lipgloss.NewStyle().Foreground(white).Background(secondary)
1003	s.ButtonBlur = s.Base.Background(bgSubtle)
1004
1005	// Borders
1006	s.BorderFocus = lipgloss.NewStyle().BorderForeground(borderFocus).Border(lipgloss.RoundedBorder()).Padding(1, 2)
1007
1008	// Editor
1009	s.EditorPromptNormalFocused = lipgloss.NewStyle().Foreground(greenDark).SetString("::: ")
1010	s.EditorPromptNormalBlurred = s.EditorPromptNormalFocused.Foreground(fgMuted)
1011	s.EditorPromptYoloIconFocused = lipgloss.NewStyle().Foreground(charmtone.Oyster).Background(charmtone.Citron).Bold(true).SetString(" ! ")
1012	s.EditorPromptYoloIconBlurred = s.EditorPromptYoloIconFocused.Foreground(charmtone.Pepper).Background(charmtone.Squid)
1013	s.EditorPromptYoloDotsFocused = lipgloss.NewStyle().Foreground(charmtone.Zest).SetString(":::")
1014	s.EditorPromptYoloDotsBlurred = s.EditorPromptYoloDotsFocused.Foreground(charmtone.Squid)
1015
1016	// Logo colors
1017	s.LogoFieldColor = primary
1018	s.LogoTitleColorA = secondary
1019	s.LogoTitleColorB = primary
1020	s.LogoCharmColor = secondary
1021	s.LogoVersionColor = primary
1022
1023	// Section
1024	s.Section.Title = s.Subtle
1025	s.Section.Line = s.Base.Foreground(charmtone.Charcoal)
1026
1027	// Initialize
1028	s.Initialize.Header = s.Base
1029	s.Initialize.Content = s.Muted
1030	s.Initialize.Accent = s.Base.Foreground(greenDark)
1031
1032	// LSP and MCP status.
1033	s.ItemOfflineIcon = lipgloss.NewStyle().Foreground(charmtone.Squid).SetString("●")
1034	s.ItemBusyIcon = s.ItemOfflineIcon.Foreground(charmtone.Citron)
1035	s.ItemErrorIcon = s.ItemOfflineIcon.Foreground(charmtone.Coral)
1036	s.ItemOnlineIcon = s.ItemOfflineIcon.Foreground(charmtone.Guac)
1037
1038	// LSP
1039	s.LSP.ErrorDiagnostic = s.Base.Foreground(redDark)
1040	s.LSP.WarningDiagnostic = s.Base.Foreground(warning)
1041	s.LSP.HintDiagnostic = s.Base.Foreground(fgHalfMuted)
1042	s.LSP.InfoDiagnostic = s.Base.Foreground(info)
1043
1044	// Files
1045	s.Files.Path = s.Muted
1046	s.Files.Additions = s.Base.Foreground(greenDark)
1047	s.Files.Deletions = s.Base.Foreground(redDark)
1048
1049	// Chat
1050	messageFocussedBorder := lipgloss.Border{
1051		Left: "▌",
1052	}
1053
1054	s.Chat.Message.NoContent = lipgloss.NewStyle().Foreground(fgBase)
1055	s.Chat.Message.UserBlurred = s.Chat.Message.NoContent.PaddingLeft(1).BorderLeft(true).
1056		BorderForeground(primary).BorderStyle(normalBorder)
1057	s.Chat.Message.UserFocused = s.Chat.Message.NoContent.PaddingLeft(1).BorderLeft(true).
1058		BorderForeground(primary).BorderStyle(messageFocussedBorder)
1059	s.Chat.Message.AssistantBlurred = s.Chat.Message.NoContent.PaddingLeft(2)
1060	s.Chat.Message.AssistantFocused = s.Chat.Message.NoContent.PaddingLeft(1).BorderLeft(true).
1061		BorderForeground(greenDark).BorderStyle(messageFocussedBorder)
1062	s.Chat.Message.Thinking = lipgloss.NewStyle().MaxHeight(10)
1063	s.Chat.Message.ErrorTag = lipgloss.NewStyle().Padding(0, 1).
1064		Background(red).Foreground(white)
1065	s.Chat.Message.ErrorTitle = lipgloss.NewStyle().Foreground(fgHalfMuted)
1066	s.Chat.Message.ErrorDetails = lipgloss.NewStyle().Foreground(fgSubtle)
1067
1068	// Message item styles
1069	s.Chat.Message.Attachment = lipgloss.NewStyle().Background(bgSubtle)
1070	s.Chat.Message.ToolCallFocused = s.Muted.PaddingLeft(1).
1071		BorderStyle(messageFocussedBorder).
1072		BorderLeft(true).
1073		BorderForeground(greenDark)
1074	s.Chat.Message.ToolCallBlurred = s.Muted.PaddingLeft(2)
1075	s.Chat.Message.SectionHeader = s.Base.PaddingLeft(2)
1076
1077	// Thinking section styles
1078	s.Chat.Message.ThinkingBox = s.Subtle.Background(bgBaseLighter)
1079	s.Chat.Message.ThinkingTruncationHint = s.Muted
1080	s.Chat.Message.ThinkingFooterTitle = s.Muted
1081	s.Chat.Message.ThinkingFooterDuration = s.Subtle
1082	s.Chat.Message.ThinkingFooterCancelled = s.Subtle
1083
1084	// Section metadata styles
1085	s.Chat.Message.SectionIcon = s.Subtle
1086	s.Chat.Message.SectionModel = s.Muted
1087	s.Chat.Message.SectionDuration = s.Subtle
1088
1089	// Text selection.
1090	s.TextSelection = lipgloss.NewStyle().Foreground(charmtone.Salt).Background(charmtone.Charple)
1091
1092	// Dialog styles
1093	s.Dialog.Title = base.Padding(0, 1).Foreground(primary)
1094	s.Dialog.View = base.Border(lipgloss.RoundedBorder()).BorderForeground(borderFocus)
1095	s.Dialog.HelpView = base.Padding(0, 1).AlignHorizontal(lipgloss.Left)
1096	s.Dialog.Help.ShortKey = base.Foreground(fgMuted)
1097	s.Dialog.Help.ShortDesc = base.Foreground(fgSubtle)
1098	s.Dialog.Help.ShortSeparator = base.Foreground(border)
1099	s.Dialog.Help.Ellipsis = base.Foreground(border)
1100	s.Dialog.Help.FullKey = base.Foreground(fgMuted)
1101	s.Dialog.Help.FullDesc = base.Foreground(fgSubtle)
1102	s.Dialog.Help.FullSeparator = base.Foreground(border)
1103	s.Dialog.NormalItem = base.Padding(0, 1).Foreground(fgBase)
1104	s.Dialog.SelectedItem = base.Padding(0, 1).Background(primary).Foreground(fgBase)
1105	s.Dialog.InputPrompt = base.Padding(0, 1)
1106
1107	return s
1108}
1109
1110// Helper functions for style pointers
1111func boolPtr(b bool) *bool       { return &b }
1112func stringPtr(s string) *string { return &s }
1113func uintPtr(u uint) *uint       { return &u }
1114func chromaStyle(style ansi.StylePrimitive) string {
1115	var s string
1116
1117	if style.Color != nil {
1118		s = *style.Color
1119	}
1120	if style.BackgroundColor != nil {
1121		if s != "" {
1122			s += " "
1123		}
1124		s += "bg:" + *style.BackgroundColor
1125	}
1126	if style.Italic != nil && *style.Italic {
1127		if s != "" {
1128			s += " "
1129		}
1130		s += "italic"
1131	}
1132	if style.Bold != nil && *style.Bold {
1133		if s != "" {
1134			s += " "
1135		}
1136		s += "bold"
1137	}
1138	if style.Underline != nil && *style.Underline {
1139		if s != "" {
1140			s += " "
1141		}
1142		s += "underline"
1143	}
1144
1145	return s
1146}