Skip to content

Commit 82de143

Browse files
feat: themes (#113)
* feat: themes * feat: flexoki theme * feat: onedark theme * feat: monokai pro theme * feat: opencode theme (default) * feat: dracula theme * feat: tokyonight theme * feat: tron theme * some small fixes --------- Co-authored-by: Kujtim Hoxha <[email protected]>
1 parent 61d9dc9 commit 82de143

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+4595
-1923
lines changed

cmd/schema/main.go

+23
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,29 @@ func generateSchema() map[string]any {
9898
},
9999
}
100100

101+
schema["properties"].(map[string]any)["tui"] = map[string]any{
102+
"type": "object",
103+
"description": "Terminal User Interface configuration",
104+
"properties": map[string]any{
105+
"theme": map[string]any{
106+
"type": "string",
107+
"description": "TUI theme name",
108+
"default": "opencode",
109+
"enum": []string{
110+
"opencode",
111+
"catppuccin",
112+
"dracula",
113+
"flexoki",
114+
"gruvbox",
115+
"monokai",
116+
"onedark",
117+
"tokyonight",
118+
"tron",
119+
},
120+
},
121+
},
122+
}
123+
101124
// Add MCP servers
102125
schema["properties"].(map[string]any)["mcpServers"] = map[string]any{
103126
"type": "object",

internal/app/app.go

+19
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/opencode-ai/opencode/internal/message"
1717
"github.com/opencode-ai/opencode/internal/permission"
1818
"github.com/opencode-ai/opencode/internal/session"
19+
"github.com/opencode-ai/opencode/internal/tui/theme"
1920
)
2021

2122
type App struct {
@@ -49,6 +50,9 @@ func New(ctx context.Context, conn *sql.DB) (*App, error) {
4950
LSPClients: make(map[string]*lsp.Client),
5051
}
5152

53+
// Initialize theme based on configuration
54+
app.initTheme()
55+
5256
// Initialize LSP clients in the background
5357
go app.initLSPClients(ctx)
5458

@@ -73,6 +77,21 @@ func New(ctx context.Context, conn *sql.DB) (*App, error) {
7377
return app, nil
7478
}
7579

80+
// initTheme sets the application theme based on the configuration
81+
func (app *App) initTheme() {
82+
cfg := config.Get()
83+
if cfg == nil || cfg.TUI.Theme == "" {
84+
return // Use default theme
85+
}
86+
87+
// Try to set the theme from config
88+
err := theme.SetTheme(cfg.TUI.Theme)
89+
if err != nil {
90+
logging.Warn("Failed to set theme from config, using default theme", "theme", cfg.TUI.Theme, "error", err)
91+
} else {
92+
logging.Debug("Set theme from config", "theme", cfg.TUI.Theme)
93+
}
94+
}
7695

7796
// Shutdown performs a clean shutdown of the application
7897
func (app *App) Shutdown() {

internal/config/config.go

+68
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
package config
33

44
import (
5+
"encoding/json"
56
"fmt"
67
"log/slog"
78
"os"
9+
"path/filepath"
810
"strings"
911

1012
"github.com/opencode-ai/opencode/internal/llm/models"
@@ -65,6 +67,11 @@ type LSPConfig struct {
6567
Options any `json:"options"`
6668
}
6769

70+
// TUIConfig defines the configuration for the Terminal User Interface.
71+
type TUIConfig struct {
72+
Theme string `json:"theme,omitempty"`
73+
}
74+
6875
// Config is the main configuration structure for the application.
6976
type Config struct {
7077
Data Data `json:"data"`
@@ -76,6 +83,7 @@ type Config struct {
7683
Debug bool `json:"debug,omitempty"`
7784
DebugLSP bool `json:"debugLSP,omitempty"`
7885
ContextPaths []string `json:"contextPaths,omitempty"`
86+
TUI TUIConfig `json:"tui"`
7987
}
8088

8189
// Application constants
@@ -203,6 +211,7 @@ func configureViper() {
203211
func setDefaults(debug bool) {
204212
viper.SetDefault("data.directory", defaultDataDirectory)
205213
viper.SetDefault("contextPaths", defaultContextPaths)
214+
viper.SetDefault("tui.theme", "opencode")
206215

207216
if debug {
208217
viper.SetDefault("debug", true)
@@ -714,3 +723,62 @@ func UpdateAgentModel(agentName AgentName, modelID models.ModelID) error {
714723

715724
return nil
716725
}
726+
727+
// UpdateTheme updates the theme in the configuration and writes it to the config file.
728+
func UpdateTheme(themeName string) error {
729+
if cfg == nil {
730+
return fmt.Errorf("config not loaded")
731+
}
732+
733+
// Update the in-memory config
734+
cfg.TUI.Theme = themeName
735+
736+
// Get the config file path
737+
configFile := viper.ConfigFileUsed()
738+
var configData []byte
739+
if configFile == "" {
740+
homeDir, err := os.UserHomeDir()
741+
if err != nil {
742+
return fmt.Errorf("failed to get home directory: %w", err)
743+
}
744+
configFile = filepath.Join(homeDir, fmt.Sprintf(".%s.json", appName))
745+
logging.Info("config file not found, creating new one", "path", configFile)
746+
configData = []byte(`{}`)
747+
} else {
748+
// Read the existing config file
749+
data, err := os.ReadFile(configFile)
750+
if err != nil {
751+
return fmt.Errorf("failed to read config file: %w", err)
752+
}
753+
configData = data
754+
}
755+
756+
// Parse the JSON
757+
var configMap map[string]interface{}
758+
if err := json.Unmarshal(configData, &configMap); err != nil {
759+
return fmt.Errorf("failed to parse config file: %w", err)
760+
}
761+
762+
// Update just the theme value
763+
tuiConfig, ok := configMap["tui"].(map[string]interface{})
764+
if !ok {
765+
// TUI config doesn't exist yet, create it
766+
configMap["tui"] = map[string]interface{}{"theme": themeName}
767+
} else {
768+
// Update existing TUI config
769+
tuiConfig["theme"] = themeName
770+
configMap["tui"] = tuiConfig
771+
}
772+
773+
// Write the updated config back to file
774+
updatedData, err := json.MarshalIndent(configMap, "", " ")
775+
if err != nil {
776+
return fmt.Errorf("failed to marshal config: %w", err)
777+
}
778+
779+
if err := os.WriteFile(configFile, updatedData, 0o644); err != nil {
780+
return fmt.Errorf("failed to write config file: %w", err)
781+
}
782+
783+
return nil
784+
}

0 commit comments

Comments
 (0)