summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authoradamdottv <[email protected]>2025-06-20 14:40:39 -0500
committeradamdottv <[email protected]>2025-06-20 15:14:22 -0500
commit4f7e4a9436673639a728ecaced1a56e96bd552f8 (patch)
treef0bf726b4f6a6d6ae2abeffc709dfdf52ecff555
parenteee396f903df22209c9d910afd67b69a91430119 (diff)
downloadopencode-4f7e4a9436673639a728ecaced1a56e96bd552f8.tar.gz
opencode-4f7e4a9436673639a728ecaced1a56e96bd552f8.zip
feat(tui): custom themes
-rw-r--r--packages/tui/internal/app/app.go12
-rw-r--r--packages/tui/internal/theme/README.md110
-rw-r--r--packages/tui/internal/theme/ayu.go276
-rw-r--r--packages/tui/internal/theme/everforest.go298
-rw-r--r--packages/tui/internal/theme/loader.go394
-rw-r--r--packages/tui/internal/theme/loader_test.go136
-rw-r--r--packages/tui/internal/theme/manager.go133
-rw-r--r--packages/tui/internal/theme/opencode.go297
-rw-r--r--packages/tui/internal/theme/theme.go75
-rw-r--r--packages/tui/internal/theme/themes/ayu.json81
-rw-r--r--packages/tui/internal/theme/themes/everforest.json242
-rw-r--r--packages/tui/internal/theme/themes/opencode.json246
-rw-r--r--packages/tui/internal/theme/themes/tokyonight.json244
-rw-r--r--packages/tui/internal/theme/tokyonight.go295
-rw-r--r--packages/tui/internal/tui/tui.go2
15 files changed, 1467 insertions, 1374 deletions
diff --git a/packages/tui/internal/app/app.go b/packages/tui/internal/app/app.go
index 787a12844..72d30d196 100644
--- a/packages/tui/internal/app/app.go
+++ b/packages/tui/internal/app/app.go
@@ -60,6 +60,9 @@ func New(
if err != nil {
return nil, err
}
+ if configResponse.StatusCode() != 200 || configResponse.JSON200 == nil {
+ return nil, fmt.Errorf("failed to get config: %d", configResponse.StatusCode())
+ }
configInfo := configResponse.JSON200
if configInfo.Keybinds == nil {
leader := "ctrl+x"
@@ -85,6 +88,15 @@ func New(
appState.Model = strings.Join(splits[1:], "/")
}
+ // Load themes from all directories
+ if err := theme.LoadThemesFromDirectories(
+ appInfo.Path.Config,
+ appInfo.Path.Root,
+ appInfo.Path.Cwd,
+ ); err != nil {
+ slog.Warn("Failed to load themes from directories", "error", err)
+ }
+
if appState.Theme != "" {
theme.SetTheme(appState.Theme)
}
diff --git a/packages/tui/internal/theme/README.md b/packages/tui/internal/theme/README.md
new file mode 100644
index 000000000..4d17100e0
--- /dev/null
+++ b/packages/tui/internal/theme/README.md
@@ -0,0 +1,110 @@
+# OpenCode Theme System
+
+OpenCode supports a flexible JSON-based theme system that allows users to create and customize themes easily.
+
+## Theme Loading Hierarchy
+
+Themes are loaded from multiple directories in the following order (later directories override earlier ones):
+
+1. **Built-in themes** - Embedded in the binary
+2. **User config directory** - `~/.config/opencode/themes/*.json` (or `$XDG_CONFIG_HOME/opencode/themes/*.json`)
+3. **Project root directory** - `<project-root>/.opencode/themes/*.json`
+4. **Current working directory** - `./.opencode/themes/*.json`
+
+If multiple directories contain a theme with the same name, the theme from the directory with higher priority will be used.
+
+## Creating a Custom Theme
+
+To create a custom theme, create a JSON file in one of the theme directories:
+
+```bash
+# For user-wide themes
+mkdir -p ~/.config/opencode/themes
+vim ~/.config/opencode/themes/my-theme.json
+
+# For project-specific themes
+mkdir -p .opencode/themes
+vim .opencode/themes/my-theme.json
+```
+
+## Theme JSON Format
+
+Themes use a flexible JSON format with support for:
+
+- **Hex colors**: `"#ffffff"`
+- **ANSI colors**: `3` (0-255)
+- **Color references**: `"primary"` or custom definitions
+- **Dark/light variants**: `{"dark": "#000", "light": "#fff"}`
+
+### Example Theme
+
+```json
+{
+ "$schema": "../theme.schema.json",
+ "defs": {
+ "brandColor": "#ff6600",
+ "darkBg": "#1a1a1a",
+ "lightBg": "#ffffff"
+ },
+ "theme": {
+ "primary": "brandColor",
+ "secondary": {
+ "dark": "#0066ff",
+ "light": "#0044cc"
+ },
+ "accent": 208,
+ "text": {
+ "dark": "#ffffff",
+ "light": "#000000"
+ },
+ "background": {
+ "dark": "darkBg",
+ "light": "lightBg"
+ },
+ "border": {
+ "dark": 8,
+ "light": 7
+ },
+ "borderActive": "primary"
+ }
+}
+```
+
+### Color Definitions
+
+The `defs` section (optional) allows you to define reusable colors that can be referenced in the theme.
+
+### Required Theme Colors
+
+At minimum, a theme must define:
+- `primary`
+- `secondary`
+- `accent`
+- `text`
+- `textMuted`
+- `background`
+
+### All Available Theme Colors
+
+- **Base colors**: `primary`, `secondary`, `accent`
+- **Status colors**: `error`, `warning`, `success`, `info`
+- **Text colors**: `text`, `textMuted`
+- **Background colors**: `background`, `backgroundPanel`, `backgroundElement`
+- **Border colors**: `border`, `borderActive`, `borderSubtle`
+- **Diff colors**: `diffAdded`, `diffRemoved`, `diffContext`, etc.
+- **Markdown colors**: `markdownHeading`, `markdownLink`, `markdownCode`, etc.
+- **Syntax colors**: `syntaxKeyword`, `syntaxFunction`, `syntaxString`, etc.
+
+See the JSON schema file for a complete list of available colors.
+
+## Built-in Themes
+
+OpenCode comes with several built-in themes:
+- `opencode` - Default OpenCode theme
+- `tokyonight` - Tokyo Night theme
+- `everforest` - Everforest theme
+- `ayu` - Ayu dark theme
+
+## Using a Theme
+
+To use a theme, set it in your OpenCode configuration or select it from the theme dialog in the TUI. \ No newline at end of file
diff --git a/packages/tui/internal/theme/ayu.go b/packages/tui/internal/theme/ayu.go
deleted file mode 100644
index 2cb24a710..000000000
--- a/packages/tui/internal/theme/ayu.go
+++ /dev/null
@@ -1,276 +0,0 @@
-package theme
-
-import (
- "github.com/charmbracelet/lipgloss/v2"
- "github.com/charmbracelet/lipgloss/v2/compat"
-)
-
-// AyuTheme implements the Theme interface with Ayu Dark colors.
-// It provides a modern dark theme inspired by the Ayu color scheme.
-type AyuTheme struct {
- BaseTheme
-}
-
-// NewAyuTheme creates a new instance of the Ayu Dark theme.
-func NewAyuTheme() *AyuTheme {
- // Ayu Dark color palette
- // Base background colors
- darkBg := "#0B0E14" // App background
- darkBgAlt := "#0D1017" // Editor background
- darkLine := "#11151C" // UI line separators
- darkPanel := "#0F131A" // UI panel background
-
- // Text colors
- darkFg := "#BFBDB6" // Primary text
- darkFgMuted := "#565B66" // Muted text
- darkGutter := "#6C7380" // Gutter text
-
- // Syntax highlighting colors
- darkTag := "#39BAE6" // Tags and attributes
- darkFunc := "#FFB454" // Functions
- darkEntity := "#59C2FF" // Entities and variables
- darkString := "#AAD94C" // Strings
- darkRegexp := "#95E6CB" // Regular expressions
- darkMarkup := "#F07178" // Markup elements
- darkKeyword := "#FF8F40" // Keywords
- darkSpecial := "#E6B673" // Special characters
- darkComment := "#ACB6BF" // Comments
- darkConstant := "#D2A6FF" // Constants
- darkOperator := "#F29668" // Operators
-
- // Version control colors
- darkAdded := "#7FD962" // Added lines
- darkRemoved := "#F26D78" // Removed lines
-
- // Accent colors
- darkAccent := "#E6B450" // Primary accent
- darkError := "#D95757" // Error color
-
- // Active state colors
- darkIndentActive := "#6C7380" // Active indent guides
-
- theme := &AyuTheme{}
-
- // Base colors
- theme.PrimaryColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkEntity),
- Light: lipgloss.Color(darkEntity),
- }
- theme.SecondaryColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkConstant),
- Light: lipgloss.Color(darkConstant),
- }
- theme.AccentColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkAccent),
- Light: lipgloss.Color(darkAccent),
- }
-
- // Status colors
- theme.ErrorColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkError),
- Light: lipgloss.Color(darkError),
- }
- theme.WarningColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkSpecial),
- Light: lipgloss.Color(darkSpecial),
- }
- theme.SuccessColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkAdded),
- Light: lipgloss.Color(darkAdded),
- }
- theme.InfoColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkTag),
- Light: lipgloss.Color(darkTag),
- }
-
- // Text colors
- theme.TextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkFg),
- Light: lipgloss.Color(darkFg),
- }
- theme.TextMutedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkFgMuted),
- Light: lipgloss.Color(darkFgMuted),
- }
-
- // Background colors
- theme.BackgroundColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkBg),
- Light: lipgloss.Color(darkBg),
- }
- theme.BackgroundPanelColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkBgAlt),
- Light: lipgloss.Color(darkBgAlt),
- }
- theme.BackgroundElementColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkPanel),
- Light: lipgloss.Color(darkPanel),
- }
-
- // Border colors
- theme.BorderColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkGutter),
- Light: lipgloss.Color(darkGutter),
- }
- theme.BorderActiveColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkIndentActive),
- Light: lipgloss.Color(darkIndentActive),
- }
- theme.BorderSubtleColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkLine),
- Light: lipgloss.Color(darkLine),
- }
-
- // Diff view colors
- theme.DiffAddedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkAdded),
- Light: lipgloss.Color(darkAdded),
- }
- theme.DiffRemovedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkRemoved),
- Light: lipgloss.Color(darkRemoved),
- }
- theme.DiffContextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkFgMuted),
- Light: lipgloss.Color(darkFgMuted),
- }
- theme.DiffHunkHeaderColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkGutter),
- Light: lipgloss.Color(darkGutter),
- }
- theme.DiffHighlightAddedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkAdded),
- Light: lipgloss.Color(darkAdded),
- }
- theme.DiffHighlightRemovedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkRemoved),
- Light: lipgloss.Color(darkRemoved),
- }
- theme.DiffAddedBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#1a2b1a"),
- Light: lipgloss.Color("#1a2b1a"),
- }
- theme.DiffRemovedBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#2b1a1a"),
- Light: lipgloss.Color("#2b1a1a"),
- }
- theme.DiffContextBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkBgAlt),
- Light: lipgloss.Color(darkBgAlt),
- }
- theme.DiffLineNumberColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkGutter),
- Light: lipgloss.Color(darkGutter),
- }
- theme.DiffAddedLineNumberBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#152b15"),
- Light: lipgloss.Color("#152b15"),
- }
- theme.DiffRemovedLineNumberBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#2b1515"),
- Light: lipgloss.Color("#2b1515"),
- }
-
- // Markdown colors
- theme.MarkdownTextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkFg),
- Light: lipgloss.Color(darkFg),
- }
- theme.MarkdownHeadingColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkFunc),
- Light: lipgloss.Color(darkFunc),
- }
- theme.MarkdownLinkColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkTag),
- Light: lipgloss.Color(darkTag),
- }
- theme.MarkdownLinkTextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkEntity),
- Light: lipgloss.Color(darkEntity),
- }
- theme.MarkdownCodeColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkString),
- Light: lipgloss.Color(darkString),
- }
- theme.MarkdownBlockQuoteColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkSpecial),
- Light: lipgloss.Color(darkSpecial),
- }
- theme.MarkdownEmphColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkKeyword),
- Light: lipgloss.Color(darkKeyword),
- }
- theme.MarkdownStrongColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkMarkup),
- Light: lipgloss.Color(darkMarkup),
- }
- theme.MarkdownHorizontalRuleColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkGutter),
- Light: lipgloss.Color(darkGutter),
- }
- theme.MarkdownListItemColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkOperator),
- Light: lipgloss.Color(darkOperator),
- }
- theme.MarkdownListEnumerationColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkConstant),
- Light: lipgloss.Color(darkConstant),
- }
- theme.MarkdownImageColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkRegexp),
- Light: lipgloss.Color(darkRegexp),
- }
- theme.MarkdownImageTextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkEntity),
- Light: lipgloss.Color(darkEntity),
- }
- theme.MarkdownCodeBlockColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkString),
- Light: lipgloss.Color(darkString),
- }
-
- // Syntax highlighting colors
- theme.SyntaxCommentColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkComment),
- Light: lipgloss.Color(darkComment),
- }
- theme.SyntaxKeywordColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkKeyword),
- Light: lipgloss.Color(darkKeyword),
- }
- theme.SyntaxFunctionColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkFunc),
- Light: lipgloss.Color(darkFunc),
- }
- theme.SyntaxVariableColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkEntity),
- Light: lipgloss.Color(darkEntity),
- }
- theme.SyntaxStringColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkString),
- Light: lipgloss.Color(darkString),
- }
- theme.SyntaxNumberColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkConstant),
- Light: lipgloss.Color(darkConstant),
- }
- theme.SyntaxTypeColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkSpecial),
- Light: lipgloss.Color(darkSpecial),
- }
- theme.SyntaxOperatorColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkOperator),
- Light: lipgloss.Color(darkOperator),
- }
- theme.SyntaxPunctuationColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkFg),
- Light: lipgloss.Color(darkFg),
- }
-
- return theme
-}
-
-func init() {
- // Register the Ayu theme with the theme manager
- RegisterTheme("ayu", NewAyuTheme())
-}
diff --git a/packages/tui/internal/theme/everforest.go b/packages/tui/internal/theme/everforest.go
deleted file mode 100644
index 60ea3849a..000000000
--- a/packages/tui/internal/theme/everforest.go
+++ /dev/null
@@ -1,298 +0,0 @@
-package theme
-
-import (
- "github.com/charmbracelet/lipgloss/v2"
- "github.com/charmbracelet/lipgloss/v2/compat"
-)
-
-// EverforestTheme implements the Theme interface with Everforest colors.
-// It provides both dark and light variants with Medium (default) contrast.
-type EverforestTheme struct {
- BaseTheme
-}
-
-// NewEverforestTheme creates a new instance of the Everforest Medium theme.
-func NewEverforestTheme() *EverforestTheme {
- // Everforest color palette - Medium variant
- // Official colors from https://github.com/sainnhe/everforest/wiki
- // Dark mode colors - using Everforest:Dark Medium contrast palette
- darkStep1 := "#2d353b" // App background
- darkStep2 := "#333c43" // Subtle background
- darkStep3 := "#343f44" // UI element background
- darkStep4 := "#3d484d" // Hovered UI element background
- darkStep5 := "#475258" // Active/Selected UI element background
- darkStep6 := "#7a8478" // Subtle borders and separators
- darkStep7 := "#859289" // UI element border and focus rings
- darkStep8 := "#9da9a0" // Hovered UI element border
- darkStep9 := "#a7c080" // Solid backgrounds
- darkStep10 := "#83c092" // Hovered solid backgrounds
- darkStep11 := "#7a8478" // Low-contrast text
- darkStep12 := "#d3c6aa" // High-contrast text
-
- // Dark mode accent colors
- darkPrimary := darkStep9 // Primary uses step 9 (green)
- darkSecondary := "#7fbbb3" // Secondary (blue)
- darkAccent := "#d699b6" // Accent (purple)
- darkRed := "#e67e80" // Error (red)
- darkOrange := "#e69875" // Warning (orange)
- darkGreen := "#a7c080" // Success (green)
- darkCyan := "#83c092" // Info (aqua)
- darkYellow := "#dbbc7f" // Emphasized text
-
- // Light mode colors for the Everforest:Light Medium contrast palette
- lightStep1 := "#fdf6e3" // App background
- lightStep2 := "#efebd4" // Subtle background
- lightStep3 := "#f4f0d9" // UI element background
- lightStep4 := "#efebd4" // Hovered UI element background
- lightStep5 := "#e6e2cc" // Active/Selected UI element background
- lightStep6 := "#a6b0a0" // Subtle borders and separators
- lightStep7 := "#939f91" // UI element border and focus rings
- lightStep8 := "#829181" // Hovered UI element border
- lightStep9 := "#8da101" // Solid backgrounds
- lightStep10 := "#35a77c" // Hovered solid backgrounds
- lightStep11 := "#a6b0a0" // Low-contrast text
- lightStep12 := "#5c6a72" // High-contrast text
-
- // Light mode accent colors
- lightPrimary := lightStep9 // Primary uses step 9 (green)
- lightSecondary := "#3a94c5" // Secondary blue
- lightAccent := "#df69ba" // Accent purple
- lightRed := "#f85552" // Error red
- lightOrange := "#f57d26" // Warning orange
- lightGreen := "#8da101" // Success green
- lightCyan := "#35a77c" // Info aqua
- lightYellow := "#dfa000" // Emphasized text
-
- // Unused variables. These could be used for hover states
- _ = darkStep4
- _ = darkStep5
- _ = darkStep10
- _ = lightStep4
- _ = lightStep5
- _ = lightStep10
-
- theme := &EverforestTheme{}
-
- // Base colors
- theme.PrimaryColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkPrimary),
- Light: lipgloss.Color(lightPrimary),
- }
- theme.SecondaryColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkSecondary),
- Light: lipgloss.Color(lightSecondary),
- }
- theme.AccentColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkAccent),
- Light: lipgloss.Color(lightAccent),
- }
-
- // Status colors
- theme.ErrorColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkRed),
- Light: lipgloss.Color(lightRed),
- }
- theme.WarningColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkOrange),
- Light: lipgloss.Color(lightOrange),
- }
- theme.SuccessColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkGreen),
- Light: lipgloss.Color(lightGreen),
- }
- theme.InfoColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkCyan),
- Light: lipgloss.Color(lightCyan),
- }
-
- // Text colors
- theme.TextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep12),
- Light: lipgloss.Color(lightStep12),
- }
- theme.TextMutedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep11),
- Light: lipgloss.Color(lightStep11),
- }
-
- // Background colors
- theme.BackgroundColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep1),
- Light: lipgloss.Color(lightStep1),
- }
- theme.BackgroundPanelColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep2),
- Light: lipgloss.Color(lightStep2),
- }
- theme.BackgroundElementColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep3),
- Light: lipgloss.Color(lightStep3),
- }
-
- // Border colors
- theme.BorderColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep7),
- Light: lipgloss.Color(lightStep7),
- }
- theme.BorderActiveColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep8),
- Light: lipgloss.Color(lightStep8),
- }
- theme.BorderSubtleColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep6),
- Light: lipgloss.Color(lightStep6),
- }
-
- // Diff view colors
- theme.DiffAddedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#A7C080"),
- Light: lipgloss.Color("#8DA101"),
- }
- theme.DiffRemovedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#E67E80"),
- Light: lipgloss.Color("#F85552"),
- }
- theme.DiffContextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#7A8478"),
- Light: lipgloss.Color("#A6B0A0"),
- }
- theme.DiffHunkHeaderColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#859289"),
- Light: lipgloss.Color("#939F91"),
- }
- theme.DiffHighlightAddedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#A7C080"),
- Light: lipgloss.Color("#8DA101"),
- }
- theme.DiffHighlightRemovedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#E67E80"),
- Light: lipgloss.Color("#F85552"),
- }
- theme.DiffAddedBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#425047"),
- Light: lipgloss.Color("#F0F1D2"),
- }
- theme.DiffRemovedBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#543A48"),
- Light: lipgloss.Color("#FBE3DA"),
- }
- theme.DiffContextBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep2),
- Light: lipgloss.Color(lightStep2),
- }
- theme.DiffLineNumberColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep3),
- Light: lipgloss.Color(lightStep3),
- }
- theme.DiffAddedLineNumberBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#3A4A3F"),
- Light: lipgloss.Color("#E8F2D1"),
- }
- theme.DiffRemovedLineNumberBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#4A3A40"),
- Light: lipgloss.Color("#FBDAD2"),
- }
-
- // Markdown colors
- theme.MarkdownTextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep12),
- Light: lipgloss.Color(lightStep12),
- }
- theme.MarkdownHeadingColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkSecondary),
- Light: lipgloss.Color(lightSecondary),
- }
- theme.MarkdownLinkColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkPrimary),
- Light: lipgloss.Color(lightPrimary),
- }
- theme.MarkdownLinkTextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkCyan),
- Light: lipgloss.Color(lightCyan),
- }
- theme.MarkdownCodeColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkGreen),
- Light: lipgloss.Color(lightGreen),
- }
- theme.MarkdownBlockQuoteColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkYellow),
- Light: lipgloss.Color(lightYellow),
- }
- theme.MarkdownEmphColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkYellow),
- Light: lipgloss.Color(lightYellow),
- }
- theme.MarkdownStrongColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkAccent),
- Light: lipgloss.Color(lightAccent),
- }
- theme.MarkdownHorizontalRuleColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep11),
- Light: lipgloss.Color(lightStep11),
- }
- theme.MarkdownListItemColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkPrimary),
- Light: lipgloss.Color(lightPrimary),
- }
- theme.MarkdownListEnumerationColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkCyan),
- Light: lipgloss.Color(lightCyan),
- }
- theme.MarkdownImageColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkPrimary),
- Light: lipgloss.Color(lightPrimary),
- }
- theme.MarkdownImageTextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkCyan),
- Light: lipgloss.Color(lightCyan),
- }
- theme.MarkdownCodeBlockColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep12),
- Light: lipgloss.Color(lightStep12),
- }
-
- // Syntax highlighting colors
- theme.SyntaxCommentColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep11),
- Light: lipgloss.Color(lightStep11),
- }
- theme.SyntaxKeywordColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkPrimary),
- Light: lipgloss.Color(lightPrimary),
- }
- theme.SyntaxFunctionColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkSecondary),
- Light: lipgloss.Color(lightSecondary),
- }
- theme.SyntaxVariableColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkRed),
- Light: lipgloss.Color(lightRed),
- }
- theme.SyntaxStringColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkGreen),
- Light: lipgloss.Color(lightGreen),
- }
- theme.SyntaxNumberColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkAccent),
- Light: lipgloss.Color(lightAccent),
- }
- theme.SyntaxTypeColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkYellow),
- Light: lipgloss.Color(lightYellow),
- }
- theme.SyntaxOperatorColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkCyan),
- Light: lipgloss.Color(lightCyan),
- }
- theme.SyntaxPunctuationColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep12),
- Light: lipgloss.Color(lightStep12),
- }
-
- return theme
-}
-
-func init() {
- // Register the Everforest theme with the theme manager
- RegisterTheme("everforest", NewEverforestTheme())
-}
diff --git a/packages/tui/internal/theme/loader.go b/packages/tui/internal/theme/loader.go
new file mode 100644
index 000000000..424dc5f56
--- /dev/null
+++ b/packages/tui/internal/theme/loader.go
@@ -0,0 +1,394 @@
+package theme
+
+import (
+ "embed"
+ "encoding/json"
+ "fmt"
+ "image/color"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/charmbracelet/lipgloss/v2"
+ "github.com/charmbracelet/lipgloss/v2/compat"
+)
+
+//go:embed themes/*.json
+var themesFS embed.FS
+
+type JSONTheme struct {
+ Defs map[string]any `json:"defs,omitempty"`
+ Theme map[string]any `json:"theme"`
+}
+
+type LoadedTheme struct {
+ BaseTheme
+ name string
+}
+
+type colorRef struct {
+ value any
+ resolved bool
+}
+
+func LoadThemesFromJSON() error {
+ entries, err := themesFS.ReadDir("themes")
+ if err != nil {
+ return fmt.Errorf("failed to read themes directory: %w", err)
+ }
+
+ for _, entry := range entries {
+ if !strings.HasSuffix(entry.Name(), ".json") {
+ continue
+ }
+ themeName := strings.TrimSuffix(entry.Name(), ".json")
+ data, err := themesFS.ReadFile(filepath.Join("themes", entry.Name()))
+ if err != nil {
+ return fmt.Errorf("failed to read theme file %s: %w", entry.Name(), err)
+ }
+ theme, err := parseJSONTheme(themeName, data)
+ if err != nil {
+ return fmt.Errorf("failed to parse theme %s: %w", themeName, err)
+ }
+ RegisterTheme(themeName, theme)
+ }
+
+ return nil
+}
+
+// LoadThemesFromDirectories loads themes from user directories in the correct override order.
+// The hierarchy is (from lowest to highest priority):
+// 1. Built-in themes (embedded)
+// 2. USER_CONFIG/opencode/themes/*.json
+// 3. PROJECT_ROOT/.opencode/themes/*.json
+// 4. CWD/.opencode/themes/*.json
+func LoadThemesFromDirectories(userConfig, projectRoot, cwd string) error {
+ if err := LoadThemesFromJSON(); err != nil {
+ return fmt.Errorf("failed to load built-in themes: %w", err)
+ }
+
+ dirs := []string{
+ filepath.Join(userConfig, "themes"),
+ filepath.Join(projectRoot, ".opencode", "themes"),
+ }
+ if cwd != projectRoot {
+ dirs = append(dirs, filepath.Join(cwd, ".opencode", "themes"))
+ }
+
+ for _, dir := range dirs {
+ if err := loadThemesFromDirectory(dir); err != nil {
+ fmt.Printf("Warning: Failed to load themes from %s: %v\n", dir, err)
+ }
+ }
+
+ return nil
+}
+
+func loadThemesFromDirectory(dir string) error {
+ if _, err := os.Stat(dir); os.IsNotExist(err) {
+ return nil // Directory doesn't exist, which is fine
+ }
+
+ entries, err := os.ReadDir(dir)
+ if err != nil {
+ return fmt.Errorf("failed to read directory: %w", err)
+ }
+
+ for _, entry := range entries {
+ if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") {
+ continue
+ }
+
+ themeName := strings.TrimSuffix(entry.Name(), ".json")
+ filePath := filepath.Join(dir, entry.Name())
+
+ data, err := os.ReadFile(filePath)
+ if err != nil {
+ fmt.Printf("Warning: Failed to read theme file %s: %v\n", filePath, err)
+ continue
+ }
+
+ theme, err := parseJSONTheme(themeName, data)
+ if err != nil {
+ fmt.Printf("Warning: Failed to parse theme %s: %v\n", filePath, err)
+ continue
+ }
+
+ RegisterTheme(themeName, theme)
+ }
+
+ return nil
+}
+
+func parseJSONTheme(name string, data []byte) (Theme, error) {
+ var jsonTheme JSONTheme
+ if err := json.Unmarshal(data, &jsonTheme); err != nil {
+ return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
+ }
+ theme := &LoadedTheme{
+ name: name,
+ }
+ colorMap := make(map[string]*colorRef)
+ for key, value := range jsonTheme.Defs {
+ colorMap[key] = &colorRef{value: value, resolved: false}
+ }
+ for key, value := range jsonTheme.Theme {
+ colorMap[key] = &colorRef{value: value, resolved: false}
+ }
+ resolver := &colorResolver{
+ colors: colorMap,
+ visited: make(map[string]bool),
+ }
+ for key, value := range jsonTheme.Theme {
+ resolved, err := resolver.resolveColor(key, value)
+ if err != nil {
+ return nil, fmt.Errorf("failed to resolve color %s: %w", key, err)
+ }
+ adaptiveColor, err := parseResolvedColor(resolved)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse color %s: %w", key, err)
+ }
+ if err := setThemeColor(theme, key, adaptiveColor); err != nil {
+ return nil, fmt.Errorf("failed to set color %s: %w", key, err)
+ }
+ }
+
+ return theme, nil
+}
+
+type colorResolver struct {
+ colors map[string]*colorRef
+ visited map[string]bool
+}
+
+func (r *colorResolver) resolveColor(key string, value any) (any, error) {
+ if r.visited[key] {
+ return nil, fmt.Errorf("circular reference detected for color %s", key)
+ }
+ r.visited[key] = true
+ defer func() { r.visited[key] = false }()
+
+ switch v := value.(type) {
+ case string:
+ if strings.HasPrefix(v, "#") {
+ return v, nil
+ }
+ return r.resolveReference(v)
+ case float64:
+ return v, nil
+ case map[string]any:
+ resolved := make(map[string]any)
+
+ if dark, ok := v["dark"]; ok {
+ resolvedDark, err := r.resolveColorValue(dark)
+ if err != nil {
+ return nil, fmt.Errorf("failed to resolve dark variant: %w", err)
+ }
+ resolved["dark"] = resolvedDark
+ }
+
+ if light, ok := v["light"]; ok {
+ resolvedLight, err := r.resolveColorValue(light)
+ if err != nil {
+ return nil, fmt.Errorf("failed to resolve light variant: %w", err)
+ }
+ resolved["light"] = resolvedLight
+ }
+
+ return resolved, nil
+ default:
+ return nil, fmt.Errorf("invalid color value type: %T", value)
+ }
+}
+
+func (r *colorResolver) resolveColorValue(value any) (any, error) {
+ switch v := value.(type) {
+ case string:
+ if strings.HasPrefix(v, "#") {
+ return v, nil
+ }
+ return r.resolveReference(v)
+ case float64:
+ return v, nil
+ default:
+ return nil, fmt.Errorf("invalid color value type: %T", value)
+ }
+}
+
+func (r *colorResolver) resolveReference(ref string) (any, error) {
+ colorRef, exists := r.colors[ref]
+ if !exists {
+ return nil, fmt.Errorf("color reference '%s' not found", ref)
+ }
+
+ if colorRef.resolved {
+ return colorRef.value, nil
+ }
+
+ resolved, err := r.resolveColor(ref, colorRef.value)
+ if err != nil {
+ return nil, err
+ }
+
+ colorRef.value = resolved
+ colorRef.resolved = true
+
+ return resolved, nil
+}
+
+func parseResolvedColor(value any) (compat.AdaptiveColor, error) {
+ switch v := value.(type) {
+ case string:
+ return compat.AdaptiveColor{
+ Dark: lipgloss.Color(v),
+ Light: lipgloss.Color(v),
+ }, nil
+ case float64:
+ colorStr := fmt.Sprintf("%d", int(v))
+ return compat.AdaptiveColor{
+ Dark: lipgloss.Color(colorStr),
+ Light: lipgloss.Color(colorStr),
+ }, nil
+ case map[string]any:
+ dark, darkOk := v["dark"]
+ light, lightOk := v["light"]
+
+ if !darkOk || !lightOk {
+ return compat.AdaptiveColor{}, fmt.Errorf("color object must have both 'dark' and 'light' keys")
+ }
+ darkColor, err := parseColorValue(dark)
+ if err != nil {
+ return compat.AdaptiveColor{}, fmt.Errorf("failed to parse dark color: %w", err)
+ }
+ lightColor, err := parseColorValue(light)
+ if err != nil {
+ return compat.AdaptiveColor{}, fmt.Errorf("failed to parse light color: %w", err)
+ }
+ return compat.AdaptiveColor{
+ Dark: darkColor,
+ Light: lightColor,
+ }, nil
+ default:
+ return compat.AdaptiveColor{}, fmt.Errorf("invalid resolved color type: %T", value)
+ }
+}
+
+func parseColorValue(value any) (color.Color, error) {
+ switch v := value.(type) {
+ case string:
+ return lipgloss.Color(v), nil
+ case float64:
+ return lipgloss.Color(fmt.Sprintf("%d", int(v))), nil
+ default:
+ return nil, fmt.Errorf("invalid color value type: %T", value)
+ }
+}
+
+func setThemeColor(theme *LoadedTheme, key string, color compat.AdaptiveColor) error {
+ switch key {
+ case "primary":
+ theme.PrimaryColor = color
+ case "secondary":
+ theme.SecondaryColor = color
+ case "accent":
+ theme.AccentColor = color
+ case "error":
+ theme.ErrorColor = color
+ case "warning":
+ theme.WarningColor = color
+ case "success":
+ theme.SuccessColor = color
+ case "info":
+ theme.InfoColor = color
+ case "text":
+ theme.TextColor = color
+ case "textMuted":
+ theme.TextMutedColor = color
+ case "background":
+ theme.BackgroundColor = color
+ case "backgroundPanel":
+ theme.BackgroundPanelColor = color
+ case "backgroundElement":
+ theme.BackgroundElementColor = color
+ case "border":
+ theme.BorderColor = color
+ case "borderActive":
+ theme.BorderActiveColor = color
+ case "borderSubtle":
+ theme.BorderSubtleColor = color
+ case "diffAdded":
+ theme.DiffAddedColor = color
+ case "diffRemoved":
+ theme.DiffRemovedColor = color
+ case "diffContext":
+ theme.DiffContextColor = color
+ case "diffHunkHeader":
+ theme.DiffHunkHeaderColor = color
+ case "diffHighlightAdded":
+ theme.DiffHighlightAddedColor = color
+ case "diffHighlightRemoved":
+ theme.DiffHighlightRemovedColor = color
+ case "diffAddedBg":
+ theme.DiffAddedBgColor = color
+ case "diffRemovedBg":
+ theme.DiffRemovedBgColor = color
+ case "diffContextBg":
+ theme.DiffContextBgColor = color
+ case "diffLineNumber":
+ theme.DiffLineNumberColor = color
+ case "diffAddedLineNumberBg":
+ theme.DiffAddedLineNumberBgColor = color
+ case "diffRemovedLineNumberBg":
+ theme.DiffRemovedLineNumberBgColor = color
+ case "markdownText":
+ theme.MarkdownTextColor = color
+ case "markdownHeading":
+ theme.MarkdownHeadingColor = color
+ case "markdownLink":
+ theme.MarkdownLinkColor = color
+ case "markdownLinkText":
+ theme.MarkdownLinkTextColor = color
+ case "markdownCode":
+ theme.MarkdownCodeColor = color
+ case "markdownBlockQuote":
+ theme.MarkdownBlockQuoteColor = color
+ case "markdownEmph":
+ theme.MarkdownEmphColor = color
+ case "markdownStrong":
+ theme.MarkdownStrongColor = color
+ case "markdownHorizontalRule":
+ theme.MarkdownHorizontalRuleColor = color
+ case "markdownListItem":
+ theme.MarkdownListItemColor = color
+ case "markdownListEnumeration":
+ theme.MarkdownListEnumerationColor = color
+ case "markdownImage":
+ theme.MarkdownImageColor = color
+ case "markdownImageText":
+ theme.MarkdownImageTextColor = color
+ case "markdownCodeBlock":
+ theme.MarkdownCodeBlockColor = color
+ case "syntaxComment":
+ theme.SyntaxCommentColor = color
+ case "syntaxKeyword":
+ theme.SyntaxKeywordColor = color
+ case "syntaxFunction":
+ theme.SyntaxFunctionColor = color
+ case "syntaxVariable":
+ theme.SyntaxVariableColor = color
+ case "syntaxString":
+ theme.SyntaxStringColor = color
+ case "syntaxNumber":
+ theme.SyntaxNumberColor = color
+ case "syntaxType":
+ theme.SyntaxTypeColor = color
+ case "syntaxOperator":
+ theme.SyntaxOperatorColor = color
+ case "syntaxPunctuation":
+ theme.SyntaxPunctuationColor = color
+ default:
+ // Ignore unknown keys for forward compatibility
+ return nil
+ }
+ return nil
+}
diff --git a/packages/tui/internal/theme/loader_test.go b/packages/tui/internal/theme/loader_test.go
new file mode 100644
index 000000000..c854cac0f
--- /dev/null
+++ b/packages/tui/internal/theme/loader_test.go
@@ -0,0 +1,136 @@
+package theme
+
+import (
+ "os"
+ "path/filepath"
+ "slices"
+ "testing"
+)
+
+func TestLoadThemesFromJSON(t *testing.T) {
+ // Test loading themes
+ err := LoadThemesFromJSON()
+ if err != nil {
+ t.Fatalf("Failed to load themes: %v", err)
+ }
+
+ // Check that themes were loaded
+ themes := AvailableThemes()
+ if len(themes) == 0 {
+ t.Fatal("No themes were loaded")
+ }
+
+ // Check for expected themes
+ expectedThemes := []string{"tokyonight", "opencode", "everforest", "ayu", "example"}
+ for _, expected := range expectedThemes {
+ found := slices.Contains(themes, expected)
+ if !found {
+ t.Errorf("Expected theme %s not found", expected)
+ }
+ }
+
+ // Test getting a specific theme
+ tokyonight := GetTheme("tokyonight")
+ if tokyonight == nil {
+ t.Fatal("Failed to get tokyonight theme")
+ }
+
+ // Test theme colors
+ primary := tokyonight.Primary()
+ if primary.Dark == nil || primary.Light == nil {
+ t.Error("Primary color not properly set")
+ }
+}
+
+func TestColorReferenceResolution(t *testing.T) {
+ // Test the example theme which uses references
+ example := GetTheme("example")
+ if example == nil {
+ t.Fatal("Failed to get example theme")
+ }
+
+ // Check that brandBlue reference was resolved
+ primary := example.Primary()
+ if primary.Dark == nil || primary.Light == nil {
+ t.Error("Primary color (brandBlue reference) not resolved")
+ }
+
+ // Check that nested reference (borderActive -> primary -> brandBlue) works
+ borderActive := example.BorderActive()
+ if borderActive.Dark == nil || borderActive.Light == nil {
+ t.Error("BorderActive color (nested reference) not resolved")
+ }
+}
+
+func TestLoadThemesFromDirectories(t *testing.T) {
+ // Create temporary directories for testing
+ tempDir := t.TempDir()
+
+ userConfig := filepath.Join(tempDir, "config")
+ projectRoot := filepath.Join(tempDir, "project")
+ cwd := filepath.Join(tempDir, "cwd")
+
+ // Create theme directories
+ os.MkdirAll(filepath.Join(userConfig, "opencode", "themes"), 0755)
+ os.MkdirAll(filepath.Join(projectRoot, ".opencode", "themes"), 0755)
+ os.MkdirAll(filepath.Join(cwd, ".opencode", "themes"), 0755)
+
+ // Create test themes with same name to test override behavior
+ testTheme1 := `{
+ "theme": {
+ "primary": "#111111",
+ "secondary": "#222222",
+ "accent": "#333333",
+ "text": "#ffffff",
+ "textMuted": "#cccccc",
+ "background": "#000000"
+ }
+ }`
+
+ testTheme2 := `{
+ "theme": {
+ "primary": "#444444",
+ "secondary": "#555555",
+ "accent": "#666666",
+ "text": "#ffffff",
+ "textMuted": "#cccccc",
+ "background": "#000000"
+ }
+ }`
+
+ testTheme3 := `{
+ "theme": {
+ "primary": "#777777",
+ "secondary": "#888888",
+ "accent": "#999999",
+ "text": "#ffffff",
+ "textMuted": "#cccccc",
+ "background": "#000000"
+ }
+ }`
+
+ // Write themes to different directories
+ os.WriteFile(filepath.Join(userConfig, "opencode", "themes", "override-test.json"), []byte(testTheme1), 0644)
+ os.WriteFile(filepath.Join(projectRoot, ".opencode", "themes", "override-test.json"), []byte(testTheme2), 0644)
+ os.WriteFile(filepath.Join(cwd, ".opencode", "themes", "override-test.json"), []byte(testTheme3), 0644)
+
+ // Load themes
+ err := LoadThemesFromDirectories(userConfig, projectRoot, cwd)
+ if err != nil {
+ t.Fatalf("Failed to load themes from directories: %v", err)
+ }
+
+ // Check that the theme from CWD (highest priority) won
+ overrideTheme := GetTheme("override-test")
+ if overrideTheme == nil {
+ t.Fatal("Failed to get override-test theme")
+ }
+
+ // The primary color should be from testTheme3 (#777777)
+ primary := overrideTheme.Primary()
+ // We can't directly check the color value, but we can verify it was loaded
+ if primary.Dark == nil || primary.Light == nil {
+ t.Error("Override theme not properly loaded")
+ }
+}
+
diff --git a/packages/tui/internal/theme/manager.go b/packages/tui/internal/theme/manager.go
index aaf0db26e..87fbc131d 100644
--- a/packages/tui/internal/theme/manager.go
+++ b/packages/tui/internal/theme/manager.go
@@ -2,13 +2,11 @@ package theme
import (
"fmt"
- "log/slog"
"slices"
"strings"
"sync"
"github.com/alecthomas/chroma/v2/styles"
- // "github.com/alecthomas/chroma/v2/styles"
)
// Manager handles theme registration, selection, and retrieval.
@@ -25,9 +23,6 @@ var globalManager = &Manager{
currentName: "",
}
-// Default theme instance for custom theme defaulting
-var defaultThemeColors = NewOpenCodeTheme()
-
// RegisterTheme adds a new theme to the registry.
// If this is the first theme registered, it becomes the default.
func RegisterTheme(name string, theme Theme) {
@@ -89,6 +84,7 @@ func AvailableThemes() []string {
names = append(names, name)
}
slices.SortFunc(names, func(a, b string) int {
+ // list system theme first
if a == "opencode" {
return -1
} else if b == "opencode" {
@@ -107,130 +103,3 @@ func GetTheme(name string) Theme {
return globalManager.themes[name]
}
-
-// LoadCustomTheme creates a new theme instance based on the custom theme colors
-// defined in the configuration. It uses the default OpenCode theme as a base
-// and overrides colors that are specified in the customTheme map.
-func LoadCustomTheme(customTheme map[string]any) (Theme, error) {
- // Create a new theme based on the default OpenCode theme
- theme := NewOpenCodeTheme()
-
- // Process each color in the custom theme map
- for key, value := range customTheme {
- adaptiveColor, err := ParseAdaptiveColor(value)
- if err != nil {
- slog.Warn("Invalid color definition in custom theme", "key", key, "error", err)
- continue // Skip this color but continue processing others
- }
-
- // Set the color in the theme based on the key
- switch strings.ToLower(key) {
- case "primary":
- theme.PrimaryColor = adaptiveColor
- case "secondary":
- theme.SecondaryColor = adaptiveColor
- case "accent":
- theme.AccentColor = adaptiveColor
- case "error":
- theme.ErrorColor = adaptiveColor
- case "warning":
- theme.WarningColor = adaptiveColor
- case "success":
- theme.SuccessColor = adaptiveColor
- case "info":
- theme.InfoColor = adaptiveColor
- case "text":
- theme.TextColor = adaptiveColor
- case "textmuted":
- theme.TextMutedColor = adaptiveColor
- case "background":
- theme.BackgroundColor = adaptiveColor
- case "backgroundsubtle":
- theme.BackgroundPanelColor = adaptiveColor
- case "backgroundelement":
- theme.BackgroundElementColor = adaptiveColor
- case "border":
- theme.BorderColor = adaptiveColor
- case "borderactive":
- theme.BorderActiveColor = adaptiveColor
- case "bordersubtle":
- theme.BorderSubtleColor = adaptiveColor
- case "diffadded":
- theme.DiffAddedColor = adaptiveColor
- case "diffremoved":
- theme.DiffRemovedColor = adaptiveColor
- case "diffcontext":
- theme.DiffContextColor = adaptiveColor
- case "diffhunkheader":
- theme.DiffHunkHeaderColor = adaptiveColor
- case "diffhighlightadded":
- theme.DiffHighlightAddedColor = adaptiveColor
- case "diffhighlightremoved":
- theme.DiffHighlightRemovedColor = adaptiveColor
- case "diffaddedbg":
- theme.DiffAddedBgColor = adaptiveColor
- case "diffremovedbg":
- theme.DiffRemovedBgColor = adaptiveColor
- case "diffcontextbg":
- theme.DiffContextBgColor = adaptiveColor
- case "difflinenumber":
- theme.DiffLineNumberColor = adaptiveColor
- case "diffaddedlinenumberbg":
- theme.DiffAddedLineNumberBgColor = adaptiveColor
- case "diffremovedlinenumberbg":
- theme.DiffRemovedLineNumberBgColor = adaptiveColor
- case "syntaxcomment":
- theme.SyntaxCommentColor = adaptiveColor
- case "syntaxkeyword":
- theme.SyntaxKeywordColor = adaptiveColor
- case "syntaxfunction":
- theme.SyntaxFunctionColor = adaptiveColor
- case "syntaxvariable":
- theme.SyntaxVariableColor = adaptiveColor
- case "syntaxstring":
- theme.SyntaxStringColor = adaptiveColor
- case "syntaxnumber":
- theme.SyntaxNumberColor = adaptiveColor
- case "syntaxtype":
- theme.SyntaxTypeColor = adaptiveColor
- case "syntaxoperator":
- theme.SyntaxOperatorColor = adaptiveColor
- case "syntaxpunctuation":
- theme.SyntaxPunctuationColor = adaptiveColor
- case "markdowntext":
- theme.MarkdownTextColor = adaptiveColor
- case "markdownheading":
- theme.MarkdownHeadingColor = adaptiveColor
- case "markdownlink":
- theme.MarkdownLinkColor = adaptiveColor
- case "markdownlinktext":
- theme.MarkdownLinkTextColor = adaptiveColor
- case "markdowncode":
- theme.MarkdownCodeColor = adaptiveColor
- case "markdownblockquote":
- theme.MarkdownBlockQuoteColor = adaptiveColor
- case "markdownemph":
- theme.MarkdownEmphColor = adaptiveColor
- case "markdownstrong":
- theme.MarkdownStrongColor = adaptiveColor
- case "markdownhorizontalrule":
- theme.MarkdownHorizontalRuleColor = adaptiveColor
- case "markdownlistitem":
- theme.MarkdownListItemColor = adaptiveColor
- case "markdownlistitemenum":
- theme.MarkdownListEnumerationColor = adaptiveColor
- case "markdownimage":
- theme.MarkdownImageColor = adaptiveColor
- case "markdownimagetext":
- theme.MarkdownImageTextColor = adaptiveColor
- case "markdowncodeblock":
- theme.MarkdownCodeBlockColor = adaptiveColor
- case "markdownlistenumeration":
- theme.MarkdownListEnumerationColor = adaptiveColor
- default:
- slog.Warn("Unknown color key in custom theme", "key", key)
- }
- }
-
- return theme, nil
-}
diff --git a/packages/tui/internal/theme/opencode.go b/packages/tui/internal/theme/opencode.go
deleted file mode 100644
index a9d3f3b41..000000000
--- a/packages/tui/internal/theme/opencode.go
+++ /dev/null
@@ -1,297 +0,0 @@
-package theme
-
-import (
- "github.com/charmbracelet/lipgloss/v2"
- "github.com/charmbracelet/lipgloss/v2/compat"
-)
-
-// OpenCodeTheme implements the Theme interface with OpenCode brand colors.
-// It provides both dark and light variants.
-type OpenCodeTheme struct {
- BaseTheme
-}
-
-// NewOpenCodeTheme creates a new instance of the OpenCode theme.
-func NewOpenCodeTheme() *OpenCodeTheme {
- // OpenCode color palette with Radix-inspired scale progression
- // Dark mode colors - using a neutral gray scale as base
- darkStep1 := "#0a0a0a" // App background
- darkStep2 := "#141414" // Subtle background
- darkStep3 := "#1e1e1e" // UI element background
- darkStep4 := "#282828" // Hovered UI element background
- darkStep5 := "#323232" // Active/Selected UI element background
- darkStep6 := "#3c3c3c" // Subtle borders and separators
- darkStep7 := "#484848" // UI element border and focus rings
- darkStep8 := "#606060" // Hovered UI element border
- darkStep9 := "#fab283" // Solid backgrounds (primary orange/gold)
- darkStep10 := "#ffc09f" // Hovered solid backgrounds
- darkStep11 := "#808080" // Low-contrast text (more muted)
- darkStep12 := "#eeeeee" // High-contrast text
-
- // Dark mode accent colors
- darkPrimary := darkStep9 // Primary uses step 9 (solid background)
- darkSecondary := "#5c9cf5" // Secondary blue
- darkAccent := "#9d7cd8" // Accent purple
- darkRed := "#e06c75" // Error red
- darkOrange := "#f5a742" // Warning orange
- darkGreen := "#7fd88f" // Success green
- darkCyan := "#56b6c2" // Info cyan
- darkYellow := "#e5c07b" // Emphasized text
-
- // Light mode colors - using a neutral gray scale as base
- lightStep1 := "#ffffff" // App background
- lightStep2 := "#fafafa" // Subtle background
- lightStep3 := "#f5f5f5" // UI element background
- lightStep4 := "#ebebeb" // Hovered UI element background
- lightStep5 := "#e1e1e1" // Active/Selected UI element background
- lightStep6 := "#d4d4d4" // Subtle borders and separators
- lightStep7 := "#b8b8b8" // UI element border and focus rings
- lightStep8 := "#a0a0a0" // Hovered UI element border
- lightStep9 := "#3b7dd8" // Solid backgrounds (primary blue)
- lightStep10 := "#2968c3" // Hovered solid backgrounds
- lightStep11 := "#8a8a8a" // Low-contrast text (more muted)
- lightStep12 := "#1a1a1a" // High-contrast text
-
- // Light mode accent colors
- lightPrimary := lightStep9 // Primary uses step 9 (solid background)
- lightSecondary := "#7b5bb6" // Secondary purple
- lightAccent := "#d68c27" // Accent orange/gold
- lightRed := "#d1383d" // Error red
- lightOrange := "#d68c27" // Warning orange
- lightGreen := "#3d9a57" // Success green
- lightCyan := "#318795" // Info cyan
- lightYellow := "#b0851f" // Emphasized text
-
- // Unused variables to avoid compiler errors (these could be used for hover states)
- _ = darkStep4
- _ = darkStep5
- _ = darkStep10
- _ = lightStep4
- _ = lightStep5
- _ = lightStep10
-
- theme := &OpenCodeTheme{}
-
- // Base colors
- theme.PrimaryColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkPrimary),
- Light: lipgloss.Color(lightPrimary),
- }
- theme.SecondaryColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkSecondary),
- Light: lipgloss.Color(lightSecondary),
- }
- theme.AccentColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkAccent),
- Light: lipgloss.Color(lightAccent),
- }
-
- // Status colors
- theme.ErrorColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkRed),
- Light: lipgloss.Color(lightRed),
- }
- theme.WarningColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkOrange),
- Light: lipgloss.Color(lightOrange),
- }
- theme.SuccessColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkGreen),
- Light: lipgloss.Color(lightGreen),
- }
- theme.InfoColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkCyan),
- Light: lipgloss.Color(lightCyan),
- }
-
- // Text colors
- theme.TextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep12),
- Light: lipgloss.Color(lightStep12),
- }
- theme.TextMutedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep11),
- Light: lipgloss.Color(lightStep11),
- }
-
- // Background colors
- theme.BackgroundColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep1),
- Light: lipgloss.Color(lightStep1),
- }
- theme.BackgroundPanelColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep2),
- Light: lipgloss.Color(lightStep2),
- }
- theme.BackgroundElementColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep3),
- Light: lipgloss.Color(lightStep3),
- }
-
- // Border colors
- theme.BorderColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep7),
- Light: lipgloss.Color(lightStep7),
- }
- theme.BorderActiveColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep8),
- Light: lipgloss.Color(lightStep8),
- }
- theme.BorderSubtleColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep6),
- Light: lipgloss.Color(lightStep6),
- }
-
- // Diff view colors
- theme.DiffAddedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#478247"),
- Light: lipgloss.Color("#2E7D32"),
- }
- theme.DiffRemovedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#7C4444"),
- Light: lipgloss.Color("#C62828"),
- }
- theme.DiffContextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#a0a0a0"),
- Light: lipgloss.Color("#757575"),
- }
- theme.DiffHunkHeaderColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#a0a0a0"),
- Light: lipgloss.Color("#757575"),
- }
- theme.DiffHighlightAddedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#DAFADA"),
- Light: lipgloss.Color("#A5D6A7"),
- }
- theme.DiffHighlightRemovedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#FADADD"),
- Light: lipgloss.Color("#EF9A9A"),
- }
- theme.DiffAddedBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#303A30"),
- Light: lipgloss.Color("#E8F5E9"),
- }
- theme.DiffRemovedBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#3A3030"),
- Light: lipgloss.Color("#FFEBEE"),
- }
- theme.DiffContextBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep2),
- Light: lipgloss.Color(lightStep2),
- }
- theme.DiffLineNumberColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep3),
- Light: lipgloss.Color(lightStep3),
- }
- theme.DiffAddedLineNumberBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#293229"),
- Light: lipgloss.Color("#C8E6C9"),
- }
- theme.DiffRemovedLineNumberBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#332929"),
- Light: lipgloss.Color("#FFCDD2"),
- }
-
- // Markdown colors
- theme.MarkdownTextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep12),
- Light: lipgloss.Color(lightStep12),
- }
- theme.MarkdownHeadingColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkSecondary),
- Light: lipgloss.Color(lightSecondary),
- }
- theme.MarkdownLinkColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkPrimary),
- Light: lipgloss.Color(lightPrimary),
- }
- theme.MarkdownLinkTextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkCyan),
- Light: lipgloss.Color(lightCyan),
- }
- theme.MarkdownCodeColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkGreen),
- Light: lipgloss.Color(lightGreen),
- }
- theme.MarkdownBlockQuoteColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkYellow),
- Light: lipgloss.Color(lightYellow),
- }
- theme.MarkdownEmphColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkYellow),
- Light: lipgloss.Color(lightYellow),
- }
- theme.MarkdownStrongColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkAccent),
- Light: lipgloss.Color(lightAccent),
- }
- theme.MarkdownHorizontalRuleColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep11),
- Light: lipgloss.Color(lightStep11),
- }
- theme.MarkdownListItemColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkPrimary),
- Light: lipgloss.Color(lightPrimary),
- }
- theme.MarkdownListEnumerationColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkCyan),
- Light: lipgloss.Color(lightCyan),
- }
- theme.MarkdownImageColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkPrimary),
- Light: lipgloss.Color(lightPrimary),
- }
- theme.MarkdownImageTextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkCyan),
- Light: lipgloss.Color(lightCyan),
- }
- theme.MarkdownCodeBlockColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep12),
- Light: lipgloss.Color(lightStep12),
- }
-
- // Syntax highlighting colors
- theme.SyntaxCommentColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep11),
- Light: lipgloss.Color(lightStep11),
- }
- theme.SyntaxKeywordColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkPrimary),
- Light: lipgloss.Color(lightPrimary),
- }
- theme.SyntaxFunctionColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkPrimary),
- Light: lipgloss.Color(lightPrimary),
- }
- theme.SyntaxVariableColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkRed),
- Light: lipgloss.Color(lightRed),
- }
- theme.SyntaxStringColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkGreen),
- Light: lipgloss.Color(lightGreen),
- }
- theme.SyntaxNumberColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkAccent),
- Light: lipgloss.Color(lightAccent),
- }
- theme.SyntaxTypeColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkYellow),
- Light: lipgloss.Color(lightYellow),
- }
- theme.SyntaxOperatorColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkCyan),
- Light: lipgloss.Color(lightCyan),
- }
- theme.SyntaxPunctuationColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep12),
- Light: lipgloss.Color(lightStep12),
- }
-
- return theme
-}
-
-func init() {
- // Register the OpenCode theme with the theme manager
- RegisterTheme("opencode", NewOpenCodeTheme())
-}
diff --git a/packages/tui/internal/theme/theme.go b/packages/tui/internal/theme/theme.go
index 9b1af1cce..9b5b7b915 100644
--- a/packages/tui/internal/theme/theme.go
+++ b/packages/tui/internal/theme/theme.go
@@ -1,10 +1,6 @@
package theme
import (
- "fmt"
- "regexp"
-
- "github.com/charmbracelet/lipgloss/v2"
"github.com/charmbracelet/lipgloss/v2/compat"
)
@@ -215,74 +211,3 @@ func (t *BaseTheme) SyntaxNumber() compat.AdaptiveColor { return t.SyntaxNu
func (t *BaseTheme) SyntaxType() compat.AdaptiveColor { return t.SyntaxTypeColor }
func (t *BaseTheme) SyntaxOperator() compat.AdaptiveColor { return t.SyntaxOperatorColor }
func (t *BaseTheme) SyntaxPunctuation() compat.AdaptiveColor { return t.SyntaxPunctuationColor }
-
-// ParseAdaptiveColor parses a color value from the config file into a compat.AdaptiveColor.
-// It accepts either a string (hex color) or a map with "dark" and "light" keys.
-func ParseAdaptiveColor(value any) (compat.AdaptiveColor, error) {
- // Regular expression to validate hex color format
- hexColorRegex := regexp.MustCompile(`^#[0-9a-fA-F]{6}$`)
-
- // Case 1: String value (same color for both dark and light modes)
- if hexColor, ok := value.(string); ok {
- if !hexColorRegex.MatchString(hexColor) {
- return compat.AdaptiveColor{}, fmt.Errorf("invalid hex color format: %s", hexColor)
- }
- return compat.AdaptiveColor{
- Dark: lipgloss.Color(hexColor),
- Light: lipgloss.Color(hexColor),
- }, nil
- }
-
- // Case 2: Int value between 0 and 255
- if numericVal, ok := value.(float64); ok {
- intVal := int(numericVal)
- if intVal < 0 || intVal > 255 {
- return compat.AdaptiveColor{}, fmt.Errorf("invalid int color value (must be between 0 and 255): %d", intVal)
- }
- return compat.AdaptiveColor{
- Dark: lipgloss.Color(fmt.Sprintf("%d", intVal)),
- Light: lipgloss.Color(fmt.Sprintf("%d", intVal)),
- }, nil
- }
-
- // Case 3: Map with dark and light keys
- if colorMap, ok := value.(map[string]any); ok {
- darkVal, darkOk := colorMap["dark"]
- lightVal, lightOk := colorMap["light"]
-
- if !darkOk || !lightOk {
- return compat.AdaptiveColor{}, fmt.Errorf("color map must contain both 'dark' and 'light' keys")
- }
-
- darkHex, darkIsString := darkVal.(string)
- lightHex, lightIsString := lightVal.(string)
-
- if !darkIsString || !lightIsString {
- darkVal, darkIsNumber := darkVal.(float64)
- lightVal, lightIsNumber := lightVal.(float64)
-
- if !darkIsNumber || !lightIsNumber {
- return compat.AdaptiveColor{}, fmt.Errorf("color map values must be strings or ints")
- }
-
- darkInt := int(darkVal)
- lightInt := int(lightVal)
-
- return compat.AdaptiveColor{
- Dark: lipgloss.Color(fmt.Sprintf("%d", darkInt)),
- Light: lipgloss.Color(fmt.Sprintf("%d", lightInt)),
- }, nil
- }
-
- if !hexColorRegex.MatchString(darkHex) || !hexColorRegex.MatchString(lightHex) {
- return compat.AdaptiveColor{}, fmt.Errorf("invalid hex color format")
- }
-
- return compat.AdaptiveColor{
- Dark: lipgloss.Color(darkHex),
- Light: lipgloss.Color(lightHex),
- }, nil
- }
-
- return compat.AdaptiveColor{}, fmt.Errorf("color must be either a hex string or an object with dark/light keys")
-}
diff --git a/packages/tui/internal/theme/themes/ayu.json b/packages/tui/internal/theme/themes/ayu.json
new file mode 100644
index 000000000..3ea7a5ca3
--- /dev/null
+++ b/packages/tui/internal/theme/themes/ayu.json
@@ -0,0 +1,81 @@
+{
+ "$schema": "https://opencode.ai/theme.json",
+ "defs": {
+ "darkBg": "#0B0E14",
+ "darkBgAlt": "#0D1017",
+ "darkLine": "#11151C",
+ "darkPanel": "#0F131A",
+ "darkFg": "#BFBDB6",
+ "darkFgMuted": "#565B66",
+ "darkGutter": "#6C7380",
+ "darkTag": "#39BAE6",
+ "darkFunc": "#FFB454",
+ "darkEntity": "#59C2FF",
+ "darkString": "#AAD94C",
+ "darkRegexp": "#95E6CB",
+ "darkMarkup": "#F07178",
+ "darkKeyword": "#FF8F40",
+ "darkSpecial": "#E6B673",
+ "darkComment": "#ACB6BF",
+ "darkConstant": "#D2A6FF",
+ "darkOperator": "#F29668",
+ "darkAdded": "#7FD962",
+ "darkRemoved": "#F26D78",
+ "darkAccent": "#E6B450",
+ "darkError": "#D95757",
+ "darkIndentActive": "#6C7380"
+ },
+ "theme": {
+ "primary": "darkEntity",
+ "secondary": "darkConstant",
+ "accent": "darkAccent",
+ "error": "darkError",
+ "warning": "darkSpecial",
+ "success": "darkAdded",
+ "info": "darkTag",
+ "text": "darkFg",
+ "textMuted": "darkFgMuted",
+ "background": "darkBg",
+ "backgroundPanel": "darkPanel",
+ "backgroundElement": "darkBgAlt",
+ "border": "darkGutter",
+ "borderActive": "darkIndentActive",
+ "borderSubtle": "darkLine",
+ "diffAdded": "darkAdded",
+ "diffRemoved": "darkRemoved",
+ "diffContext": "darkComment",
+ "diffHunkHeader": "darkComment",
+ "diffHighlightAdded": "darkString",
+ "diffHighlightRemoved": "darkMarkup",
+ "diffAddedBg": "#20303b",
+ "diffRemovedBg": "#37222c",
+ "diffContextBg": "darkPanel",
+ "diffLineNumber": "darkGutter",
+ "diffAddedLineNumberBg": "#1b2b34",
+ "diffRemovedLineNumberBg": "#2d1f26",
+ "markdownText": "darkFg",
+ "markdownHeading": "darkConstant",
+ "markdownLink": "darkEntity",
+ "markdownLinkText": "darkTag",
+ "markdownCode": "darkString",
+ "markdownBlockQuote": "darkSpecial",
+ "markdownEmph": "darkSpecial",
+ "markdownStrong": "darkFunc",
+ "markdownHorizontalRule": "darkFgMuted",
+ "markdownListItem": "darkEntity",
+ "markdownListEnumeration": "darkTag",
+ "markdownImage": "darkEntity",
+ "markdownImageText": "darkTag",
+ "markdownCodeBlock": "darkFg",
+ "syntaxComment": "darkComment",
+ "syntaxKeyword": "darkKeyword",
+ "syntaxFunction": "darkFunc",
+ "syntaxVariable": "darkEntity",
+ "syntaxString": "darkString",
+ "syntaxNumber": "darkConstant",
+ "syntaxType": "darkSpecial",
+ "syntaxOperator": "darkOperator",
+ "syntaxPunctuation": "darkFg"
+ }
+}
+
diff --git a/packages/tui/internal/theme/themes/everforest.json b/packages/tui/internal/theme/themes/everforest.json
new file mode 100644
index 000000000..19cbb9aca
--- /dev/null
+++ b/packages/tui/internal/theme/themes/everforest.json
@@ -0,0 +1,242 @@
+{
+ "$schema": "https://opencode.ai/theme.json",
+ "defs": {
+ "darkStep1": "#2d353b",
+ "darkStep2": "#333c43",
+ "darkStep3": "#343f44",
+ "darkStep4": "#3d484d",
+ "darkStep5": "#475258",
+ "darkStep6": "#7a8478",
+ "darkStep7": "#859289",
+ "darkStep8": "#9da9a0",
+ "darkStep9": "#a7c080",
+ "darkStep10": "#83c092",
+ "darkStep11": "#7a8478",
+ "darkStep12": "#d3c6aa",
+ "darkRed": "#e67e80",
+ "darkOrange": "#e69875",
+ "darkGreen": "#a7c080",
+ "darkCyan": "#83c092",
+ "darkYellow": "#dbbc7f",
+ "lightStep1": "#fdf6e3",
+ "lightStep2": "#efebd4",
+ "lightStep3": "#f4f0d9",
+ "lightStep4": "#efebd4",
+ "lightStep5": "#e6e2cc",
+ "lightStep6": "#a6b0a0",
+ "lightStep7": "#939f91",
+ "lightStep8": "#829181",
+ "lightStep9": "#8da101",
+ "lightStep10": "#35a77c",
+ "lightStep11": "#a6b0a0",
+ "lightStep12": "#5c6a72",
+ "lightRed": "#f85552",
+ "lightOrange": "#f57d26",
+ "lightGreen": "#8da101",
+ "lightCyan": "#35a77c",
+ "lightYellow": "#dfa000"
+ },
+ "theme": {
+ "primary": {
+ "dark": "darkStep9",
+ "light": "lightStep9"
+ },
+ "secondary": {
+ "dark": "#7fbbb3",
+ "light": "#3a94c5"
+ },
+ "accent": {
+ "dark": "#d699b6",
+ "light": "#df69ba"
+ },
+ "error": {
+ "dark": "darkRed",
+ "light": "lightRed"
+ },
+ "warning": {
+ "dark": "darkOrange",
+ "light": "lightOrange"
+ },
+ "success": {
+ "dark": "darkGreen",
+ "light": "lightGreen"
+ },
+ "info": {
+ "dark": "darkCyan",
+ "light": "lightCyan"
+ },
+ "text": {
+ "dark": "darkStep12",
+ "light": "lightStep12"
+ },
+ "textMuted": {
+ "dark": "darkStep11",
+ "light": "lightStep11"
+ },
+ "background": {
+ "dark": "darkStep1",
+ "light": "lightStep1"
+ },
+ "backgroundPanel": {
+ "dark": "darkStep2",
+ "light": "lightStep2"
+ },
+ "backgroundElement": {
+ "dark": "darkStep3",
+ "light": "lightStep3"
+ },
+ "border": {
+ "dark": "darkStep7",
+ "light": "lightStep7"
+ },
+ "borderActive": {
+ "dark": "darkStep8",
+ "light": "lightStep8"
+ },
+ "borderSubtle": {
+ "dark": "darkStep6",
+ "light": "lightStep6"
+ },
+ "diffAdded": {
+ "dark": "#4fd6be",
+ "light": "#1e725c"
+ },
+ "diffRemoved": {
+ "dark": "#c53b53",
+ "light": "#c53b53"
+ },
+ "diffContext": {
+ "dark": "#828bb8",
+ "light": "#7086b5"
+ },
+ "diffHunkHeader": {
+ "dark": "#828bb8",
+ "light": "#7086b5"
+ },
+ "diffHighlightAdded": {
+ "dark": "#b8db87",
+ "light": "#4db380"
+ },
+ "diffHighlightRemoved": {
+ "dark": "#e26a75",
+ "light": "#f52a65"
+ },
+ "diffAddedBg": {
+ "dark": "#20303b",
+ "light": "#d5e5d5"
+ },
+ "diffRemovedBg": {
+ "dark": "#37222c",
+ "light": "#f7d8db"
+ },
+ "diffContextBg": {
+ "dark": "darkStep2",
+ "light": "lightStep2"
+ },
+ "diffLineNumber": {
+ "dark": "darkStep3",
+ "light": "lightStep3"
+ },
+ "diffAddedLineNumberBg": {
+ "dark": "#1b2b34",
+ "light": "#c5d5c5"
+ },
+ "diffRemovedLineNumberBg": {
+ "dark": "#2d1f26",
+ "light": "#e7c8cb"
+ },
+ "markdownText": {
+ "dark": "darkStep12",
+ "light": "lightStep12"
+ },
+ "markdownHeading": {
+ "dark": "#d699b6",
+ "light": "#df69ba"
+ },
+ "markdownLink": {
+ "dark": "darkStep9",
+ "light": "lightStep9"
+ },
+ "markdownLinkText": {
+ "dark": "darkCyan",
+ "light": "lightCyan"
+ },
+ "markdownCode": {
+ "dark": "darkGreen",
+ "light": "lightGreen"
+ },
+ "markdownBlockQuote": {
+ "dark": "darkYellow",
+ "light": "lightYellow"
+ },
+ "markdownEmph": {
+ "dark": "darkYellow",
+ "light": "lightYellow"
+ },
+ "markdownStrong": {
+ "dark": "darkOrange",
+ "light": "lightOrange"
+ },
+ "markdownHorizontalRule": {
+ "dark": "darkStep11",
+ "light": "lightStep11"
+ },
+ "markdownListItem": {
+ "dark": "darkStep9",
+ "light": "lightStep9"
+ },
+ "markdownListEnumeration": {
+ "dark": "darkCyan",
+ "light": "lightCyan"
+ },
+ "markdownImage": {
+ "dark": "darkStep9",
+ "light": "lightStep9"
+ },
+ "markdownImageText": {
+ "dark": "darkCyan",
+ "light": "lightCyan"
+ },
+ "markdownCodeBlock": {
+ "dark": "darkStep12",
+ "light": "lightStep12"
+ },
+ "syntaxComment": {
+ "dark": "darkStep11",
+ "light": "lightStep11"
+ },
+ "syntaxKeyword": {
+ "dark": "#d699b6",
+ "light": "#df69ba"
+ },
+ "syntaxFunction": {
+ "dark": "darkStep9",
+ "light": "lightStep9"
+ },
+ "syntaxVariable": {
+ "dark": "darkRed",
+ "light": "lightRed"
+ },
+ "syntaxString": {
+ "dark": "darkGreen",
+ "light": "lightGreen"
+ },
+ "syntaxNumber": {
+ "dark": "darkOrange",
+ "light": "lightOrange"
+ },
+ "syntaxType": {
+ "dark": "darkYellow",
+ "light": "lightYellow"
+ },
+ "syntaxOperator": {
+ "dark": "darkCyan",
+ "light": "lightCyan"
+ },
+ "syntaxPunctuation": {
+ "dark": "darkStep12",
+ "light": "lightStep12"
+ }
+ }
+}
+
diff --git a/packages/tui/internal/theme/themes/opencode.json b/packages/tui/internal/theme/themes/opencode.json
new file mode 100644
index 000000000..bdef5ab3e
--- /dev/null
+++ b/packages/tui/internal/theme/themes/opencode.json
@@ -0,0 +1,246 @@
+{
+ "$schema": "https://opencode.ai/theme.json",
+ "defs": {
+ "darkStep1": "#0a0a0a",
+ "darkStep2": "#141414",
+ "darkStep3": "#1e1e1e",
+ "darkStep4": "#282828",
+ "darkStep5": "#323232",
+ "darkStep6": "#3c3c3c",
+ "darkStep7": "#484848",
+ "darkStep8": "#606060",
+ "darkStep9": "#fab283",
+ "darkStep10": "#ffc09f",
+ "darkStep11": "#808080",
+ "darkStep12": "#eeeeee",
+ "darkSecondary": "#5c9cf5",
+ "darkAccent": "#9d7cd8",
+ "darkRed": "#e06c75",
+ "darkOrange": "#f5a742",
+ "darkGreen": "#7fd88f",
+ "darkCyan": "#56b6c2",
+ "darkYellow": "#e5c07b",
+ "lightStep1": "#ffffff",
+ "lightStep2": "#fafafa",
+ "lightStep3": "#f5f5f5",
+ "lightStep4": "#ebebeb",
+ "lightStep5": "#e1e1e1",
+ "lightStep6": "#d4d4d4",
+ "lightStep7": "#b8b8b8",
+ "lightStep8": "#a0a0a0",
+ "lightStep9": "#3b7dd8",
+ "lightStep10": "#2968c3",
+ "lightStep11": "#8a8a8a",
+ "lightStep12": "#1a1a1a",
+ "lightSecondary": "#7b5bb6",
+ "lightAccent": "#d68c27",
+ "lightRed": "#d1383d",
+ "lightOrange": "#d68c27",
+ "lightGreen": "#3d9a57",
+ "lightCyan": "#318795",
+ "lightYellow": "#b0851f"
+ },
+ "theme": {
+ "primary": {
+ "dark": "darkStep9",
+ "light": "lightStep9"
+ },
+ "secondary": {
+ "dark": "darkSecondary",
+ "light": "lightSecondary"
+ },
+ "accent": {
+ "dark": "darkAccent",
+ "light": "lightAccent"
+ },
+ "error": {
+ "dark": "darkRed",
+ "light": "lightRed"
+ },
+ "warning": {
+ "dark": "darkOrange",
+ "light": "lightOrange"
+ },
+ "success": {
+ "dark": "darkGreen",
+ "light": "lightGreen"
+ },
+ "info": {
+ "dark": "darkCyan",
+ "light": "lightCyan"
+ },
+ "text": {
+ "dark": "darkStep12",
+ "light": "lightStep12"
+ },
+ "textMuted": {
+ "dark": "darkStep11",
+ "light": "lightStep11"
+ },
+ "background": {
+ "dark": "darkStep1",
+ "light": "lightStep1"
+ },
+ "backgroundPanel": {
+ "dark": "darkStep2",
+ "light": "lightStep2"
+ },
+ "backgroundElement": {
+ "dark": "darkStep3",
+ "light": "lightStep3"
+ },
+ "border": {
+ "dark": "darkStep7",
+ "light": "lightStep7"
+ },
+ "borderActive": {
+ "dark": "darkStep8",
+ "light": "lightStep8"
+ },
+ "borderSubtle": {
+ "dark": "darkStep6",
+ "light": "lightStep6"
+ },
+ "diffAdded": {
+ "dark": "#4fd6be",
+ "light": "#1e725c"
+ },
+ "diffRemoved": {
+ "dark": "#c53b53",
+ "light": "#c53b53"
+ },
+ "diffContext": {
+ "dark": "#828bb8",
+ "light": "#7086b5"
+ },
+ "diffHunkHeader": {
+ "dark": "#828bb8",
+ "light": "#7086b5"
+ },
+ "diffHighlightAdded": {
+ "dark": "#b8db87",
+ "light": "#4db380"
+ },
+ "diffHighlightRemoved": {
+ "dark": "#e26a75",
+ "light": "#f52a65"
+ },
+ "diffAddedBg": {
+ "dark": "#20303b",
+ "light": "#d5e5d5"
+ },
+ "diffRemovedBg": {
+ "dark": "#37222c",
+ "light": "#f7d8db"
+ },
+ "diffContextBg": {
+ "dark": "darkStep2",
+ "light": "lightStep2"
+ },
+ "diffLineNumber": {
+ "dark": "darkStep3",
+ "light": "lightStep3"
+ },
+ "diffAddedLineNumberBg": {
+ "dark": "#1b2b34",
+ "light": "#c5d5c5"
+ },
+ "diffRemovedLineNumberBg": {
+ "dark": "#2d1f26",
+ "light": "#e7c8cb"
+ },
+ "markdownText": {
+ "dark": "darkStep12",
+ "light": "lightStep12"
+ },
+ "markdownHeading": {
+ "dark": "darkAccent",
+ "light": "lightAccent"
+ },
+ "markdownLink": {
+ "dark": "darkStep9",
+ "light": "lightStep9"
+ },
+ "markdownLinkText": {
+ "dark": "darkCyan",
+ "light": "lightCyan"
+ },
+ "markdownCode": {
+ "dark": "darkGreen",
+ "light": "lightGreen"
+ },
+ "markdownBlockQuote": {
+ "dark": "darkYellow",
+ "light": "lightYellow"
+ },
+ "markdownEmph": {
+ "dark": "darkYellow",
+ "light": "lightYellow"
+ },
+ "markdownStrong": {
+ "dark": "darkOrange",
+ "light": "lightOrange"
+ },
+ "markdownHorizontalRule": {
+ "dark": "darkStep11",
+ "light": "lightStep11"
+ },
+ "markdownListItem": {
+ "dark": "darkStep9",
+ "light": "lightStep9"
+ },
+ "markdownListEnumeration": {
+ "dark": "darkCyan",
+ "light": "lightCyan"
+ },
+ "markdownImage": {
+ "dark": "darkStep9",
+ "light": "lightStep9"
+ },
+ "markdownImageText": {
+ "dark": "darkCyan",
+ "light": "lightCyan"
+ },
+ "markdownCodeBlock": {
+ "dark": "darkStep12",
+ "light": "lightStep12"
+ },
+ "syntaxComment": {
+ "dark": "darkStep11",
+ "light": "lightStep11"
+ },
+ "syntaxKeyword": {
+ "dark": "darkAccent",
+ "light": "lightAccent"
+ },
+ "syntaxFunction": {
+ "dark": "darkStep9",
+ "light": "lightStep9"
+ },
+ "syntaxVariable": {
+ "dark": "darkRed",
+ "light": "lightRed"
+ },
+ "syntaxString": {
+ "dark": "darkGreen",
+ "light": "lightGreen"
+ },
+ "syntaxNumber": {
+ "dark": "darkOrange",
+ "light": "lightOrange"
+ },
+ "syntaxType": {
+ "dark": "darkYellow",
+ "light": "lightYellow"
+ },
+ "syntaxOperator": {
+ "dark": "darkCyan",
+ "light": "lightCyan"
+ },
+ "syntaxPunctuation": {
+ "dark": "darkStep12",
+ "light": "lightStep12"
+ }
+ }
+}
+
diff --git a/packages/tui/internal/theme/themes/tokyonight.json b/packages/tui/internal/theme/themes/tokyonight.json
new file mode 100644
index 000000000..cfd3c7ce6
--- /dev/null
+++ b/packages/tui/internal/theme/themes/tokyonight.json
@@ -0,0 +1,244 @@
+{
+ "$schema": "https://opencode.ai/theme.json",
+ "defs": {
+ "darkStep1": "#1a1b26",
+ "darkStep2": "#1e2030",
+ "darkStep3": "#222436",
+ "darkStep4": "#292e42",
+ "darkStep5": "#3b4261",
+ "darkStep6": "#545c7e",
+ "darkStep7": "#737aa2",
+ "darkStep8": "#9099b2",
+ "darkStep9": "#82aaff",
+ "darkStep10": "#89b4fa",
+ "darkStep11": "#828bb8",
+ "darkStep12": "#c8d3f5",
+ "darkRed": "#ff757f",
+ "darkOrange": "#ff966c",
+ "darkYellow": "#ffc777",
+ "darkGreen": "#c3e88d",
+ "darkCyan": "#86e1fc",
+ "darkPurple": "#c099ff",
+ "lightStep1": "#e1e2e7",
+ "lightStep2": "#d5d6db",
+ "lightStep3": "#c8c9ce",
+ "lightStep4": "#b9bac1",
+ "lightStep5": "#a8aecb",
+ "lightStep6": "#9699a8",
+ "lightStep7": "#737a8c",
+ "lightStep8": "#5a607d",
+ "lightStep9": "#2e7de9",
+ "lightStep10": "#1a6ce7",
+ "lightStep11": "#8990a3",
+ "lightStep12": "#3760bf",
+ "lightRed": "#f52a65",
+ "lightOrange": "#b15c00",
+ "lightYellow": "#8c6c3e",
+ "lightGreen": "#587539",
+ "lightCyan": "#007197",
+ "lightPurple": "#9854f1"
+ },
+ "theme": {
+ "primary": {
+ "dark": "darkStep9",
+ "light": "lightStep9"
+ },
+ "secondary": {
+ "dark": "darkPurple",
+ "light": "lightPurple"
+ },
+ "accent": {
+ "dark": "darkOrange",
+ "light": "lightOrange"
+ },
+ "error": {
+ "dark": "darkRed",
+ "light": "lightRed"
+ },
+ "warning": {
+ "dark": "darkOrange",
+ "light": "lightOrange"
+ },
+ "success": {
+ "dark": "darkGreen",
+ "light": "lightGreen"
+ },
+ "info": {
+ "dark": "darkStep9",
+ "light": "lightStep9"
+ },
+ "text": {
+ "dark": "darkStep12",
+ "light": "lightStep12"
+ },
+ "textMuted": {
+ "dark": "darkStep11",
+ "light": "lightStep11"
+ },
+ "background": {
+ "dark": "darkStep1",
+ "light": "lightStep1"
+ },
+ "backgroundPanel": {
+ "dark": "darkStep2",
+ "light": "lightStep2"
+ },
+ "backgroundElement": {
+ "dark": "darkStep3",
+ "light": "lightStep3"
+ },
+ "border": {
+ "dark": "darkStep7",
+ "light": "lightStep7"
+ },
+ "borderActive": {
+ "dark": "darkStep8",
+ "light": "lightStep8"
+ },
+ "borderSubtle": {
+ "dark": "darkStep6",
+ "light": "lightStep6"
+ },
+ "diffAdded": {
+ "dark": "#4fd6be",
+ "light": "#1e725c"
+ },
+ "diffRemoved": {
+ "dark": "#c53b53",
+ "light": "#c53b53"
+ },
+ "diffContext": {
+ "dark": "#828bb8",
+ "light": "#7086b5"
+ },
+ "diffHunkHeader": {
+ "dark": "#828bb8",
+ "light": "#7086b5"
+ },
+ "diffHighlightAdded": {
+ "dark": "#b8db87",
+ "light": "#4db380"
+ },
+ "diffHighlightRemoved": {
+ "dark": "#e26a75",
+ "light": "#f52a65"
+ },
+ "diffAddedBg": {
+ "dark": "#20303b",
+ "light": "#d5e5d5"
+ },
+ "diffRemovedBg": {
+ "dark": "#37222c",
+ "light": "#f7d8db"
+ },
+ "diffContextBg": {
+ "dark": "darkStep2",
+ "light": "lightStep2"
+ },
+ "diffLineNumber": {
+ "dark": "darkStep3",
+ "light": "lightStep3"
+ },
+ "diffAddedLineNumberBg": {
+ "dark": "#1b2b34",
+ "light": "#c5d5c5"
+ },
+ "diffRemovedLineNumberBg": {
+ "dark": "#2d1f26",
+ "light": "#e7c8cb"
+ },
+ "markdownText": {
+ "dark": "darkStep12",
+ "light": "lightStep12"
+ },
+ "markdownHeading": {
+ "dark": "darkPurple",
+ "light": "lightPurple"
+ },
+ "markdownLink": {
+ "dark": "darkStep9",
+ "light": "lightStep9"
+ },
+ "markdownLinkText": {
+ "dark": "darkCyan",
+ "light": "lightCyan"
+ },
+ "markdownCode": {
+ "dark": "darkGreen",
+ "light": "lightGreen"
+ },
+ "markdownBlockQuote": {
+ "dark": "darkYellow",
+ "light": "lightYellow"
+ },
+ "markdownEmph": {
+ "dark": "darkYellow",
+ "light": "lightYellow"
+ },
+ "markdownStrong": {
+ "dark": "darkOrange",
+ "light": "lightOrange"
+ },
+ "markdownHorizontalRule": {
+ "dark": "darkStep11",
+ "light": "lightStep11"
+ },
+ "markdownListItem": {
+ "dark": "darkStep9",
+ "light": "lightStep9"
+ },
+ "markdownListEnumeration": {
+ "dark": "darkCyan",
+ "light": "lightCyan"
+ },
+ "markdownImage": {
+ "dark": "darkStep9",
+ "light": "lightStep9"
+ },
+ "markdownImageText": {
+ "dark": "darkCyan",
+ "light": "lightCyan"
+ },
+ "markdownCodeBlock": {
+ "dark": "darkStep12",
+ "light": "lightStep12"
+ },
+ "syntaxComment": {
+ "dark": "darkStep11",
+ "light": "lightStep11"
+ },
+ "syntaxKeyword": {
+ "dark": "darkPurple",
+ "light": "lightPurple"
+ },
+ "syntaxFunction": {
+ "dark": "darkStep9",
+ "light": "lightStep9"
+ },
+ "syntaxVariable": {
+ "dark": "darkRed",
+ "light": "lightRed"
+ },
+ "syntaxString": {
+ "dark": "darkGreen",
+ "light": "lightGreen"
+ },
+ "syntaxNumber": {
+ "dark": "darkOrange",
+ "light": "lightOrange"
+ },
+ "syntaxType": {
+ "dark": "darkYellow",
+ "light": "lightYellow"
+ },
+ "syntaxOperator": {
+ "dark": "darkCyan",
+ "light": "lightCyan"
+ },
+ "syntaxPunctuation": {
+ "dark": "darkStep12",
+ "light": "lightStep12"
+ }
+ }
+}
+
diff --git a/packages/tui/internal/theme/tokyonight.go b/packages/tui/internal/theme/tokyonight.go
deleted file mode 100644
index 1c160078a..000000000
--- a/packages/tui/internal/theme/tokyonight.go
+++ /dev/null
@@ -1,295 +0,0 @@
-package theme
-
-import (
- "github.com/charmbracelet/lipgloss/v2"
- "github.com/charmbracelet/lipgloss/v2/compat"
-)
-
-// TokyoNightTheme implements the Theme interface with Tokyo Night colors.
-// It provides both dark and light variants.
-type TokyoNightTheme struct {
- BaseTheme
-}
-
-// NewTokyoNightTheme creates a new instance of the Tokyo Night theme.
-func NewTokyoNightTheme() *TokyoNightTheme {
- // Tokyo Night color palette with Radix-inspired scale progression
- // Dark mode colors - Tokyo Night Moon variant
- darkStep1 := "#1a1b26" // App background (bg)
- darkStep2 := "#1e2030" // Subtle background (bg_dark)
- darkStep3 := "#222436" // UI element background (bg_highlight)
- darkStep4 := "#292e42" // Hovered UI element background
- darkStep5 := "#3b4261" // Active/Selected UI element background (bg_visual)
- darkStep6 := "#545c7e" // Subtle borders and separators (dark3)
- darkStep7 := "#737aa2" // UI element border and focus rings (dark5)
- darkStep8 := "#9099b2" // Hovered UI element border
- darkStep9 := "#82aaff" // Solid backgrounds (blue)
- darkStep10 := "#89b4fa" // Hovered solid backgrounds
- darkStep11 := "#828bb8" // Low-contrast text (using fg_dark for better contrast)
- darkStep12 := "#c8d3f5" // High-contrast text (fg)
-
- // Dark mode accent colors
- darkRed := "#ff757f"
- darkOrange := "#ff966c"
- darkYellow := "#ffc777"
- darkGreen := "#c3e88d"
- darkCyan := "#86e1fc"
- darkBlue := darkStep9 // Using step 9 for primary
- darkPurple := "#c099ff"
-
- // Light mode colors - Tokyo Night Day variant
- lightStep1 := "#e1e2e7" // App background
- lightStep2 := "#d5d6db" // Subtle background
- lightStep3 := "#c8c9ce" // UI element background
- lightStep4 := "#b9bac1" // Hovered UI element background
- lightStep5 := "#a8aecb" // Active/Selected UI element background
- lightStep6 := "#9699a8" // Subtle borders and separators
- lightStep7 := "#737a8c" // UI element border and focus rings
- lightStep8 := "#5a607d" // Hovered UI element border
- lightStep9 := "#2e7de9" // Solid backgrounds (blue)
- lightStep10 := "#1a6ce7" // Hovered solid backgrounds
- lightStep11 := "#8990a3" // Low-contrast text (more muted)
- lightStep12 := "#3760bf" // High-contrast text
-
- // Light mode accent colors
- lightRed := "#f52a65"
- lightOrange := "#b15c00"
- lightYellow := "#8c6c3e"
- lightGreen := "#587539"
- lightCyan := "#007197"
- lightBlue := lightStep9 // Using step 9 for primary
- lightPurple := "#9854f1"
-
- // Unused variables to avoid compiler errors (these could be used for hover states)
- _ = darkStep4
- _ = darkStep5
- _ = darkStep10
- _ = lightStep4
- _ = lightStep5
- _ = lightStep10
-
- theme := &TokyoNightTheme{}
-
- // Base colors
- theme.PrimaryColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkBlue),
- Light: lipgloss.Color(lightBlue),
- }
- theme.SecondaryColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkPurple),
- Light: lipgloss.Color(lightPurple),
- }
- theme.AccentColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkOrange),
- Light: lipgloss.Color(lightOrange),
- }
-
- // Status colors
- theme.ErrorColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkRed),
- Light: lipgloss.Color(lightRed),
- }
- theme.WarningColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkOrange),
- Light: lipgloss.Color(lightOrange),
- }
- theme.SuccessColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkGreen),
- Light: lipgloss.Color(lightGreen),
- }
- theme.InfoColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkBlue),
- Light: lipgloss.Color(lightBlue),
- }
-
- // Text colors
- theme.TextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep12),
- Light: lipgloss.Color(lightStep12),
- }
- theme.TextMutedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep11),
- Light: lipgloss.Color(lightStep11),
- }
-
- // Background colors
- theme.BackgroundColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep1),
- Light: lipgloss.Color(lightStep1),
- }
- theme.BackgroundPanelColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep2),
- Light: lipgloss.Color(lightStep2),
- }
- theme.BackgroundElementColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep3),
- Light: lipgloss.Color(lightStep3),
- }
-
- // Border colors
- theme.BorderColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep7),
- Light: lipgloss.Color(lightStep7),
- }
- theme.BorderActiveColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep8),
- Light: lipgloss.Color(lightStep8),
- }
- theme.BorderSubtleColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep6),
- Light: lipgloss.Color(lightStep6),
- }
-
- // Diff view colors
- theme.DiffAddedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#4fd6be"), // teal from palette
- Light: lipgloss.Color("#1e725c"),
- }
- theme.DiffRemovedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#c53b53"), // red1 from palette
- Light: lipgloss.Color("#c53b53"),
- }
- theme.DiffContextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#828bb8"), // fg_dark from palette
- Light: lipgloss.Color("#7086b5"),
- }
- theme.DiffHunkHeaderColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#828bb8"), // fg_dark from palette
- Light: lipgloss.Color("#7086b5"),
- }
- theme.DiffHighlightAddedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#b8db87"), // git.add from palette
- Light: lipgloss.Color("#4db380"),
- }
- theme.DiffHighlightRemovedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#e26a75"), // git.delete from palette
- Light: lipgloss.Color("#f52a65"),
- }
- theme.DiffAddedBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#20303b"),
- Light: lipgloss.Color("#d5e5d5"),
- }
- theme.DiffRemovedBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#37222c"),
- Light: lipgloss.Color("#f7d8db"),
- }
- theme.DiffContextBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep2),
- Light: lipgloss.Color(lightStep2),
- }
- theme.DiffLineNumberColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep3), // dark3 from palette
- Light: lipgloss.Color(lightStep3),
- }
- theme.DiffAddedLineNumberBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#1b2b34"),
- Light: lipgloss.Color("#c5d5c5"),
- }
- theme.DiffRemovedLineNumberBgColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("#2d1f26"),
- Light: lipgloss.Color("#e7c8cb"),
- }
-
- // Markdown colors
- theme.MarkdownTextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep12),
- Light: lipgloss.Color(lightStep12),
- }
- theme.MarkdownHeadingColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkPurple),
- Light: lipgloss.Color(lightPurple),
- }
- theme.MarkdownLinkColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkBlue),
- Light: lipgloss.Color(lightBlue),
- }
- theme.MarkdownLinkTextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkCyan),
- Light: lipgloss.Color(lightCyan),
- }
- theme.MarkdownCodeColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkGreen),
- Light: lipgloss.Color(lightGreen),
- }
- theme.MarkdownBlockQuoteColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkYellow),
- Light: lipgloss.Color(lightYellow),
- }
- theme.MarkdownEmphColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkYellow),
- Light: lipgloss.Color(lightYellow),
- }
- theme.MarkdownStrongColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkOrange),
- Light: lipgloss.Color(lightOrange),
- }
- theme.MarkdownHorizontalRuleColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep11),
- Light: lipgloss.Color(lightStep11),
- }
- theme.MarkdownListItemColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkBlue),
- Light: lipgloss.Color(lightBlue),
- }
- theme.MarkdownListEnumerationColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkCyan),
- Light: lipgloss.Color(lightCyan),
- }
- theme.MarkdownImageColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkBlue),
- Light: lipgloss.Color(lightBlue),
- }
- theme.MarkdownImageTextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkCyan),
- Light: lipgloss.Color(lightCyan),
- }
- theme.MarkdownCodeBlockColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep12),
- Light: lipgloss.Color(lightStep12),
- }
-
- // Syntax highlighting colors
- theme.SyntaxCommentColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep11),
- Light: lipgloss.Color(lightStep11),
- }
- theme.SyntaxKeywordColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkPurple),
- Light: lipgloss.Color(lightPurple),
- }
- theme.SyntaxFunctionColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkBlue),
- Light: lipgloss.Color(lightBlue),
- }
- theme.SyntaxVariableColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkRed),
- Light: lipgloss.Color(lightRed),
- }
- theme.SyntaxStringColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkGreen),
- Light: lipgloss.Color(lightGreen),
- }
- theme.SyntaxNumberColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkOrange),
- Light: lipgloss.Color(lightOrange),
- }
- theme.SyntaxTypeColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkYellow),
- Light: lipgloss.Color(lightYellow),
- }
- theme.SyntaxOperatorColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkCyan),
- Light: lipgloss.Color(lightCyan),
- }
- theme.SyntaxPunctuationColor = compat.AdaptiveColor{
- Dark: lipgloss.Color(darkStep12),
- Light: lipgloss.Color(lightStep12),
- }
-
- return theme
-}
-
-func init() {
- // Register the Tokyo Night theme with the theme manager
- RegisterTheme("tokyonight", NewTokyoNightTheme())
-}
diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go
index 5de8bfbd6..a3c83976b 100644
--- a/packages/tui/internal/tui/tui.go
+++ b/packages/tui/internal/tui/tui.go
@@ -346,7 +346,7 @@ func (a appModel) View() string {
layoutView,
a.status.View(),
}
- appView := lipgloss.JoinVertical(lipgloss.Top, components...)
+ appView := strings.Join(components, "\n")
if a.modal != nil {
appView = a.modal.Render(appView)