diff options
Diffstat (limited to 'internal/config')
| -rw-r--r-- | internal/config/config.go | 266 | ||||
| -rw-r--r-- | internal/config/init.go | 60 |
2 files changed, 0 insertions, 326 deletions
diff --git a/internal/config/config.go b/internal/config/config.go deleted file mode 100644 index 2197f5aa4..000000000 --- a/internal/config/config.go +++ /dev/null @@ -1,266 +0,0 @@ -// Package config manages application configuration from various sources. -package config - -import ( - "encoding/json" - "fmt" - "log/slog" - "os" - "os/user" - "path/filepath" - "strings" - - "github.com/spf13/viper" -) - -// Data defines storage configuration. -type Data struct { - Directory string `json:"directory,omitempty"` -} - -// TUIConfig defines the configuration for the Terminal User Interface. -type TUIConfig struct { - Theme string `json:"theme,omitempty"` - CustomTheme map[string]any `json:"customTheme,omitempty"` -} - -// ShellConfig defines the configuration for the shell used by the bash tool. -type ShellConfig struct { - Path string `json:"path,omitempty"` - Args []string `json:"args,omitempty"` -} - -// Config is the main configuration structure for the application. -type Config struct { - Data Data `json:"data"` - WorkingDir string `json:"wd,omitempty"` - Debug bool `json:"debug,omitempty"` - DebugLSP bool `json:"debugLSP,omitempty"` - ContextPaths []string `json:"contextPaths,omitempty"` - TUI TUIConfig `json:"tui"` - Shell ShellConfig `json:"shell,omitempty"` -} - -// Application constants -const ( - defaultDataDirectory = ".opencode" - defaultLogLevel = "info" - appName = "opencode" - - MaxTokensFallbackDefault = 4096 -) - -var defaultContextPaths = []string{ - ".github/copilot-instructions.md", - ".cursorrules", - ".cursor/rules/", - "CLAUDE.md", - "CLAUDE.local.md", - "CONTEXT.md", - "CONTEXT.local.md", - "opencode.md", - "opencode.local.md", - "OpenCode.md", - "OpenCode.local.md", - "OPENCODE.md", - "OPENCODE.local.md", -} - -// Global configuration instance -var cfg *Config - -// Load initializes the configuration from environment variables and config files. -// If debug is true, debug mode is enabled and log level is set to debug. -// It returns an error if configuration loading fails. -func Load(workingDir string, debug bool) (*Config, error) { - if cfg != nil { - return cfg, nil - } - - cfg = &Config{ - WorkingDir: workingDir, - } - - configureViper() - setDefaults(debug) - - // Read global config - if err := readConfig(viper.ReadInConfig()); err != nil { - return cfg, err - } - - // Load and merge local config - mergeLocalConfig(workingDir) - - // Apply configuration to the struct - if err := viper.Unmarshal(cfg); err != nil { - return cfg, fmt.Errorf("failed to unmarshal config: %w", err) - } - - defaultLevel := slog.LevelInfo - if cfg.Debug { - defaultLevel = slog.LevelDebug - } - slog.SetLogLoggerLevel(defaultLevel) - - // Validate configuration - if err := Validate(); err != nil { - return cfg, fmt.Errorf("config validation failed: %w", err) - } - return cfg, nil -} - -// configureViper sets up viper's configuration paths and environment variables. -func configureViper() { - viper.SetConfigName(fmt.Sprintf(".%s", appName)) - viper.SetConfigType("json") - viper.AddConfigPath("$HOME") - viper.AddConfigPath(fmt.Sprintf("$XDG_CONFIG_HOME/%s", appName)) - viper.AddConfigPath(fmt.Sprintf("$HOME/.config/%s", appName)) - viper.SetEnvPrefix(strings.ToUpper(appName)) - viper.AutomaticEnv() -} - -// setDefaults configures default values for configuration options. -func setDefaults(debug bool) { - viper.SetDefault("data.directory", defaultDataDirectory) - viper.SetDefault("contextPaths", defaultContextPaths) - viper.SetDefault("tui.theme", "opencode") - - if debug { - viper.SetDefault("debug", true) - viper.Set("log.level", "debug") - } else { - viper.SetDefault("debug", false) - viper.SetDefault("log.level", defaultLogLevel) - } -} - -// readConfig handles the result of reading a configuration file. -func readConfig(err error) error { - if err == nil { - return nil - } - - // It's okay if the config file doesn't exist - if _, ok := err.(viper.ConfigFileNotFoundError); ok { - return nil - } - - return fmt.Errorf("failed to read config: %w", err) -} - -// mergeLocalConfig loads and merges configuration from the local directory. -func mergeLocalConfig(workingDir string) { - local := viper.New() - local.SetConfigName(fmt.Sprintf(".%s", appName)) - local.SetConfigType("json") - local.AddConfigPath(workingDir) - - // Merge local config if it exists - if err := local.ReadInConfig(); err == nil { - viper.MergeConfigMap(local.AllSettings()) - } -} - -// Validate checks if the configuration is valid and applies defaults where needed. -func Validate() error { - if cfg == nil { - return fmt.Errorf("config not loaded") - } - - return nil -} - -// Get returns the current configuration. -// It's safe to call this function multiple times. -func Get() *Config { - return cfg -} - -// WorkingDirectory returns the current working directory from the configuration. -func WorkingDirectory() string { - if cfg == nil { - panic("config not loaded") - } - return cfg.WorkingDir -} - -// GetHostname returns the system hostname or "User" if it can't be determined -func GetHostname() (string, error) { - hostname, err := os.Hostname() - if err != nil { - return "User", err - } - return hostname, nil -} - -// GetUsername returns the current user's username -func GetUsername() (string, error) { - currentUser, err := user.Current() - if err != nil { - return "User", err - } - return currentUser.Username, nil -} - -func updateCfgFile(updateCfg func(config *Config)) error { - if cfg == nil { - return fmt.Errorf("config not loaded") - } - - // Get the config file path - configFile := viper.ConfigFileUsed() - var configData []byte - if configFile == "" { - homeDir, err := os.UserHomeDir() - if err != nil { - return fmt.Errorf("failed to get home directory: %w", err) - } - configFile = filepath.Join(homeDir, fmt.Sprintf(".%s.json", appName)) - slog.Info("config file not found, creating new one", "path", configFile) - configData = []byte(`{}`) - } else { - // Read the existing config file - data, err := os.ReadFile(configFile) - if err != nil { - return fmt.Errorf("failed to read config file: %w", err) - } - configData = data - } - - // Parse the JSON - var userCfg *Config - if err := json.Unmarshal(configData, &userCfg); err != nil { - return fmt.Errorf("failed to parse config file: %w", err) - } - - updateCfg(userCfg) - - // Write the updated config back to file - updatedData, err := json.MarshalIndent(userCfg, "", " ") - if err != nil { - return fmt.Errorf("failed to marshal config: %w", err) - } - - if err := os.WriteFile(configFile, updatedData, 0o644); err != nil { - return fmt.Errorf("failed to write config file: %w", err) - } - - return nil -} - -// UpdateTheme updates the theme in the configuration and writes it to the config file. -func UpdateTheme(themeName string) error { - if cfg == nil { - return fmt.Errorf("config not loaded") - } - - // Update the in-memory config - cfg.TUI.Theme = themeName - - // Update the file config - return updateCfgFile(func(config *Config) { - config.TUI.Theme = themeName - }) -} diff --git a/internal/config/init.go b/internal/config/init.go deleted file mode 100644 index 5f8860f52..000000000 --- a/internal/config/init.go +++ /dev/null @@ -1,60 +0,0 @@ -package config - -import ( - "fmt" - "os" - "path/filepath" -) - -const ( - // InitFlagFilename is the name of the file that indicates whether the project has been initialized - InitFlagFilename = "init" -) - -// ProjectInitFlag represents the initialization status for a project directory -type ProjectInitFlag struct { - Initialized bool `json:"initialized"` -} - -// ShouldShowInitDialog checks if the initialization dialog should be shown for the current directory -func ShouldShowInitDialog() (bool, error) { - if cfg == nil { - return false, fmt.Errorf("config not loaded") - } - - // Create the flag file path - flagFilePath := filepath.Join(cfg.Data.Directory, InitFlagFilename) - - // Check if the flag file exists - _, err := os.Stat(flagFilePath) - if err == nil { - // File exists, don't show the dialog - return false, nil - } - - // If the error is not "file not found", return the error - if !os.IsNotExist(err) { - return false, fmt.Errorf("failed to check init flag file: %w", err) - } - - // File doesn't exist, show the dialog - return true, nil -} - -// MarkProjectInitialized marks the current project as initialized -func MarkProjectInitialized() error { - if cfg == nil { - return fmt.Errorf("config not loaded") - } - // Create the flag file path - flagFilePath := filepath.Join(cfg.Data.Directory, InitFlagFilename) - - // Create an empty file to mark the project as initialized - file, err := os.Create(flagFilePath) - if err != nil { - return fmt.Errorf("failed to create init flag file: %w", err) - } - defer file.Close() - - return nil -} |
