Skip to content

Settings API

The Settings API provides comprehensive functionality for managing application settings, user preferences, and configuration options within the development environment.

Overview

The Settings API enables you to:

  • Read and write user and workspace settings
  • Define custom configuration schemas
  • Handle settings validation and migration
  • Provide settings UI and editors
  • Manage settings synchronization
  • Handle settings inheritance and overrides
  • Implement settings backup and restore
  • Provide settings search and filtering

Basic Usage

Configuration Management

typescript
import { TraeAPI } from '@trae/api';

// Settings manager implementation
class SettingsManager {
  private settingsCache: Map<string, any> = new Map();
  private settingsListeners: Map<string, ((value: any) => void)[]> = new Map();
  private customSchemas: Map<string, SettingsSchema> = new Map();
  private settingsValidators: Map<string, SettingsValidator> = new Map();

  constructor() {
    this.initializeSettings();
    this.setupConfigurationListeners();
    this.loadCustomSchemas();
    this.validateSettings();
  }

  private initializeSettings(): void {
    // Initialize default settings
    const defaultSettings = {
      'editor.fontSize': 14,
      'editor.fontFamily': 'Monaco, Consolas, monospace',
      'editor.tabSize': 2,
      'editor.insertSpaces': true,
      'editor.wordWrap': 'on',
      'editor.lineNumbers': 'on',
      'editor.minimap.enabled': true,
      'editor.formatOnSave': false,
      'editor.formatOnPaste': false,
      'workbench.colorTheme': 'dark-default',
      'workbench.iconTheme': 'default-icons',
      'workbench.startupEditor': 'welcomePage',
      'workbench.sideBar.location': 'left',
      'workbench.panel.defaultLocation': 'bottom',
      'files.autoSave': 'afterDelay',
      'files.autoSaveDelay': 1000,
      'files.exclude': {
        '**/node_modules': true,
        '**/.git': true,
        '**/.DS_Store': true
      },
      'search.exclude': {
        '**/node_modules': true,
        '**/bower_components': true
      },
      'terminal.integrated.fontSize': 12,
      'terminal.integrated.fontFamily': 'Monaco, Consolas, monospace',
      'extensions.autoUpdate': true,
      'extensions.autoCheckUpdates': true,
      'ai.enabled': true,
      'ai.completions.enabled': true,
      'ai.chat.enabled': true,
      'security.workspace.trust.enabled': true,
      'telemetry.enableTelemetry': false
    };

    // Cache default settings
    Object.entries(defaultSettings).forEach(([key, value]) => {
      this.settingsCache.set(key, value);
    });

    console.log('Default settings initialized');
  }

  private setupConfigurationListeners(): void {
    // Listen for configuration changes
    TraeAPI.workspace.onDidChangeConfiguration(event => {
      this.handleConfigurationChange(event);
    });

    // Listen for workspace folder changes
    TraeAPI.workspace.onDidChangeWorkspaceFolders(event => {
      this.handleWorkspaceFoldersChange(event);
    });
  }

  private async loadCustomSchemas(): Promise<void> {
    try {
      // Load custom settings schemas from extensions and workspace
      const extensionSchemas = await this.loadExtensionSchemas();
      const workspaceSchemas = await this.loadWorkspaceSchemas();
      
      [...extensionSchemas, ...workspaceSchemas].forEach(schema => {
        this.customSchemas.set(schema.id, schema);
        this.registerValidator(schema);
      });
      
      console.log(`Loaded ${extensionSchemas.length + workspaceSchemas.length} custom schemas`);
    } catch (error) {
      console.error('Failed to load custom schemas:', error);
    }
  }

  private async loadExtensionSchemas(): Promise<SettingsSchema[]> {
    const schemas: SettingsSchema[] = [];
    
    // Load schemas from active extensions
    const extensions = TraeAPI.extensions.all;
    
    for (const extension of extensions) {
      if (extension.packageJSON.contributes?.configuration) {
        const config = extension.packageJSON.contributes.configuration;
        
        if (Array.isArray(config)) {
          config.forEach(configItem => {
            const schema = this.parseConfigurationSchema(extension.id, configItem);
            if (schema) schemas.push(schema);
          });
        } else {
          const schema = this.parseConfigurationSchema(extension.id, config);
          if (schema) schemas.push(schema);
        }
      }
    }
    
    return schemas;
  }

  private async loadWorkspaceSchemas(): Promise<SettingsSchema[]> {
    const schemas: SettingsSchema[] = [];
    
    if (!TraeAPI.workspace.workspaceFolders) {
      return schemas;
    }
    
    for (const folder of TraeAPI.workspace.workspaceFolders) {
      try {
        // Look for settings schema files
        const schemaFiles = await TraeAPI.workspace.findFiles(
          new TraeAPI.RelativePattern(folder, '**/.trae/settings-schema.json'),
          '**/node_modules/**'
        );
        
        for (const file of schemaFiles) {
          const content = await TraeAPI.workspace.fs.readFile(file);
          const schemaData = JSON.parse(content.toString());
          
          if (this.isValidSchema(schemaData)) {
            const schema: SettingsSchema = {
              id: `workspace-${folder.name}-${Date.now()}`,
              title: schemaData.title || 'Workspace Settings',
              description: schemaData.description || '',
              properties: schemaData.properties || {},
              source: 'workspace',
              extensionId: folder.name
            };
            
            schemas.push(schema);
          }
        }
      } catch (error) {
        console.warn(`Failed to load workspace schema from ${folder.fsPath}:`, error);
      }
    }
    
    return schemas;
  }

  private parseConfigurationSchema(extensionId: string, config: any): SettingsSchema | null {
    if (!config || !config.properties) {
      return null;
    }
    
    return {
      id: `extension-${extensionId}`,
      title: config.title || `${extensionId} Settings`,
      description: config.description || '',
      properties: config.properties,
      source: 'extension',
      extensionId
    };
  }

  private registerValidator(schema: SettingsSchema): void {
    const validator: SettingsValidator = {
      schema,
      validate: (key: string, value: any): ValidationResult => {
        const property = schema.properties[key];
        if (!property) {
          return { isValid: true };
        }
        
        return this.validateProperty(key, value, property);
      }
    };
    
    this.settingsValidators.set(schema.id, validator);
  }

  private validateProperty(key: string, value: any, property: PropertySchema): ValidationResult {
    const errors: string[] = [];
    
    // Type validation
    if (property.type) {
      if (!this.validateType(value, property.type)) {
        errors.push(`Expected type ${property.type}, got ${typeof value}`);
      }
    }
    
    // Enum validation
    if (property.enum && !property.enum.includes(value)) {
      errors.push(`Value must be one of: ${property.enum.join(', ')}`);
    }
    
    // Range validation for numbers
    if (property.type === 'number') {
      if (property.minimum !== undefined && value < property.minimum) {
        errors.push(`Value must be >= ${property.minimum}`);
      }
      if (property.maximum !== undefined && value > property.maximum) {
        errors.push(`Value must be <= ${property.maximum}`);
      }
    }
    
    // String validation
    if (property.type === 'string') {
      if (property.minLength !== undefined && value.length < property.minLength) {
        errors.push(`String must be at least ${property.minLength} characters`);
      }
      if (property.maxLength !== undefined && value.length > property.maxLength) {
        errors.push(`String must be at most ${property.maxLength} characters`);
      }
      if (property.pattern && !new RegExp(property.pattern).test(value)) {
        errors.push(`String must match pattern: ${property.pattern}`);
      }
    }
    
    // Array validation
    if (property.type === 'array') {
      if (property.minItems !== undefined && value.length < property.minItems) {
        errors.push(`Array must have at least ${property.minItems} items`);
      }
      if (property.maxItems !== undefined && value.length > property.maxItems) {
        errors.push(`Array must have at most ${property.maxItems} items`);
      }
      if (property.items && property.items.type) {
        value.forEach((item: any, index: number) => {
          if (!this.validateType(item, property.items!.type!)) {
            errors.push(`Array item ${index} must be of type ${property.items!.type}`);
          }
        });
      }
    }
    
    return {
      isValid: errors.length === 0,
      errors: errors.length > 0 ? errors : undefined
    };
  }

  private validateType(value: any, expectedType: string): boolean {
    switch (expectedType) {
      case 'string':
        return typeof value === 'string';
      case 'number':
        return typeof value === 'number' && !isNaN(value);
      case 'boolean':
        return typeof value === 'boolean';
      case 'array':
        return Array.isArray(value);
      case 'object':
        return typeof value === 'object' && value !== null && !Array.isArray(value);
      default:
        return true;
    }
  }

  private async validateSettings(): Promise<void> {
    // Validate all current settings
    const allSettings = this.getAllSettings();
    const validationErrors: { [key: string]: string[] } = {};
    
    Object.entries(allSettings).forEach(([key, value]) => {
      const result = this.validateSetting(key, value);
      if (!result.isValid && result.errors) {
        validationErrors[key] = result.errors;
      }
    });
    
    if (Object.keys(validationErrors).length > 0) {
      console.warn('Settings validation errors:', validationErrors);
      
      // Optionally show validation errors to user
      const errorCount = Object.keys(validationErrors).length;
      TraeAPI.window.showWarningMessage(
        `Found ${errorCount} invalid setting${errorCount > 1 ? 's' : ''}. Check the settings for details.`
      );
    }
  }

  // Core settings operations
  get<T>(key: string, defaultValue?: T): T {
    try {
      // Check cache first
      if (this.settingsCache.has(key)) {
        return this.settingsCache.get(key) as T;
      }
      
      // Get from configuration
      const config = TraeAPI.workspace.getConfiguration();
      const value = config.get<T>(key, defaultValue as T);
      
      // Cache the value
      this.settingsCache.set(key, value);
      
      return value;
    } catch (error) {
      console.error(`Failed to get setting ${key}:`, error);
      return defaultValue as T;
    }
  }

  async set<T>(key: string, value: T, target?: TraeAPI.ConfigurationTarget): Promise<boolean> {
    try {
      // Validate the setting
      const validationResult = this.validateSetting(key, value);
      if (!validationResult.isValid) {
        const errors = validationResult.errors?.join(', ') || 'Invalid value';
        throw new Error(`Setting validation failed: ${errors}`);
      }
      
      // Determine target
      const configTarget = target || this.getDefaultTarget(key);
      
      // Update configuration
      const config = TraeAPI.workspace.getConfiguration();
      await config.update(key, value, configTarget);
      
      // Update cache
      this.settingsCache.set(key, value);
      
      // Notify listeners
      this.notifySettingChange(key, value);
      
      console.log(`Setting updated: ${key} = ${JSON.stringify(value)}`);
      return true;
    } catch (error) {
      console.error(`Failed to set setting ${key}:`, error);
      TraeAPI.window.showErrorMessage(`Failed to update setting: ${error.message}`);
      return false;
    }
  }

  async update<T>(key: string, value: T, target?: TraeAPI.ConfigurationTarget): Promise<boolean> {
    return this.set(key, value, target);
  }

  async reset(key: string, target?: TraeAPI.ConfigurationTarget): Promise<boolean> {
    try {
      const configTarget = target || this.getDefaultTarget(key);
      const config = TraeAPI.workspace.getConfiguration();
      
      await config.update(key, undefined, configTarget);
      
      // Remove from cache
      this.settingsCache.delete(key);
      
      // Get default value
      const defaultValue = this.getDefaultValue(key);
      if (defaultValue !== undefined) {
        this.settingsCache.set(key, defaultValue);
      }
      
      // Notify listeners
      this.notifySettingChange(key, defaultValue);
      
      console.log(`Setting reset: ${key}`);
      return true;
    } catch (error) {
      console.error(`Failed to reset setting ${key}:`, error);
      return false;
    }
  }

  has(key: string): boolean {
    const config = TraeAPI.workspace.getConfiguration();
    return config.has(key);
  }

  inspect<T>(key: string): SettingInspection<T> | undefined {
    try {
      const config = TraeAPI.workspace.getConfiguration();
      const inspection = config.inspect<T>(key);
      
      if (!inspection) {
        return undefined;
      }
      
      return {
        key,
        defaultValue: inspection.defaultValue,
        globalValue: inspection.globalValue,
        workspaceValue: inspection.workspaceValue,
        workspaceFolderValue: inspection.workspaceFolderValue,
        effectiveValue: this.get<T>(key),
        source: this.getSettingSource(inspection)
      };
    } catch (error) {
      console.error(`Failed to inspect setting ${key}:`, error);
      return undefined;
    }
  }

  private getSettingSource<T>(inspection: TraeAPI.WorkspaceConfiguration['inspect'] extends (...args: any[]) => infer R ? R : never): SettingSource {
    if (inspection.workspaceFolderValue !== undefined) {
      return 'workspaceFolder';
    }
    if (inspection.workspaceValue !== undefined) {
      return 'workspace';
    }
    if (inspection.globalValue !== undefined) {
      return 'user';
    }
    return 'default';
  }

  private getDefaultTarget(key: string): TraeAPI.ConfigurationTarget {
    // Determine appropriate target based on setting key
    if (key.startsWith('workbench.') || key.startsWith('editor.')) {
      return TraeAPI.ConfigurationTarget.Global;
    }
    
    if (TraeAPI.workspace.workspaceFolders && TraeAPI.workspace.workspaceFolders.length > 0) {
      return TraeAPI.ConfigurationTarget.Workspace;
    }
    
    return TraeAPI.ConfigurationTarget.Global;
  }

  private getDefaultValue(key: string): any {
    // Get default value from schema or built-in defaults
    for (const validator of this.settingsValidators.values()) {
      const property = validator.schema.properties[key];
      if (property && property.default !== undefined) {
        return property.default;
      }
    }
    
    // Check built-in defaults
    const builtInDefaults: { [key: string]: any } = {
      'editor.fontSize': 14,
      'editor.tabSize': 2,
      'editor.insertSpaces': true,
      'workbench.colorTheme': 'dark-default',
      'files.autoSave': 'afterDelay'
    };
    
    return builtInDefaults[key];
  }

  // Settings search and filtering
  searchSettings(query: string): SettingSearchResult[] {
    const results: SettingSearchResult[] = [];
    const lowercaseQuery = query.toLowerCase();
    
    // Search in all schemas
    for (const schema of this.customSchemas.values()) {
      Object.entries(schema.properties).forEach(([key, property]) => {
        const matches = [
          key.toLowerCase().includes(lowercaseQuery),
          property.title?.toLowerCase().includes(lowercaseQuery),
          property.description?.toLowerCase().includes(lowercaseQuery)
        ].some(Boolean);
        
        if (matches) {
          results.push({
            key,
            title: property.title || key,
            description: property.description || '',
            type: property.type || 'unknown',
            currentValue: this.get(key),
            defaultValue: property.default,
            schema: schema.id,
            source: schema.source
          });
        }
      });
    }
    
    return results.sort((a, b) => {
      // Sort by relevance (exact matches first)
      const aExact = a.key.toLowerCase() === lowercaseQuery;
      const bExact = b.key.toLowerCase() === lowercaseQuery;
      
      if (aExact && !bExact) return -1;
      if (!aExact && bExact) return 1;
      
      return a.key.localeCompare(b.key);
    });
  }

  getSettingsByCategory(category: string): SettingSearchResult[] {
    const results: SettingSearchResult[] = [];
    
    for (const schema of this.customSchemas.values()) {
      Object.entries(schema.properties).forEach(([key, property]) => {
        if (key.startsWith(category + '.')) {
          results.push({
            key,
            title: property.title || key,
            description: property.description || '',
            type: property.type || 'unknown',
            currentValue: this.get(key),
            defaultValue: property.default,
            schema: schema.id,
            source: schema.source
          });
        }
      });
    }
    
    return results.sort((a, b) => a.key.localeCompare(b.key));
  }

  getModifiedSettings(): SettingSearchResult[] {
    const results: SettingSearchResult[] = [];
    
    for (const schema of this.customSchemas.values()) {
      Object.entries(schema.properties).forEach(([key, property]) => {
        const inspection = this.inspect(key);
        if (inspection && inspection.source !== 'default') {
          results.push({
            key,
            title: property.title || key,
            description: property.description || '',
            type: property.type || 'unknown',
            currentValue: inspection.effectiveValue,
            defaultValue: inspection.defaultValue,
            schema: schema.id,
            source: schema.source
          });
        }
      });
    }
    
    return results.sort((a, b) => a.key.localeCompare(b.key));
  }

  // Settings validation
  validateSetting(key: string, value: any): ValidationResult {
    // Find applicable validators
    for (const validator of this.settingsValidators.values()) {
      if (validator.schema.properties[key]) {
        return validator.validate(key, value);
      }
    }
    
    // No specific validator found, basic validation
    return { isValid: true };
  }

  validateAllSettings(): { [key: string]: ValidationResult } {
    const results: { [key: string]: ValidationResult } = {};
    const allSettings = this.getAllSettings();
    
    Object.entries(allSettings).forEach(([key, value]) => {
      const result = this.validateSetting(key, value);
      if (!result.isValid) {
        results[key] = result;
      }
    });
    
    return results;
  }

  // Settings backup and restore
  async exportSettings(includeExtensions = false): Promise<SettingsExport> {
    try {
      const userSettings = this.getUserSettings();
      const workspaceSettings = this.getWorkspaceSettings();
      
      const exportData: SettingsExport = {
        version: '1.0.0',
        exportedAt: new Date().toISOString(),
        userSettings,
        workspaceSettings,
        extensions: includeExtensions ? this.getInstalledExtensions() : undefined
      };
      
      return exportData;
    } catch (error) {
      console.error('Failed to export settings:', error);
      throw error;
    }
  }

  async importSettings(settingsData: SettingsExport, options: ImportOptions = {}): Promise<boolean> {
    try {
      const {
        includeUserSettings = true,
        includeWorkspaceSettings = true,
        includeExtensions = false,
        overwriteExisting = false
      } = options;
      
      // Import user settings
      if (includeUserSettings && settingsData.userSettings) {
        await this.importUserSettings(settingsData.userSettings, overwriteExisting);
      }
      
      // Import workspace settings
      if (includeWorkspaceSettings && settingsData.workspaceSettings) {
        await this.importWorkspaceSettings(settingsData.workspaceSettings, overwriteExisting);
      }
      
      // Import extensions
      if (includeExtensions && settingsData.extensions) {
        await this.importExtensions(settingsData.extensions);
      }
      
      TraeAPI.window.showInformationMessage('Settings imported successfully!');
      return true;
    } catch (error) {
      console.error('Failed to import settings:', error);
      TraeAPI.window.showErrorMessage(`Failed to import settings: ${error.message}`);
      return false;
    }
  }

  private async importUserSettings(settings: { [key: string]: any }, overwrite: boolean): Promise<void> {
    for (const [key, value] of Object.entries(settings)) {
      if (!overwrite && this.has(key)) {
        continue; // Skip existing settings if not overwriting
      }
      
      await this.set(key, value, TraeAPI.ConfigurationTarget.Global);
    }
  }

  private async importWorkspaceSettings(settings: { [key: string]: any }, overwrite: boolean): Promise<void> {
    if (!TraeAPI.workspace.workspaceFolders) {
      console.warn('No workspace folder available for importing workspace settings');
      return;
    }
    
    for (const [key, value] of Object.entries(settings)) {
      if (!overwrite && this.has(key)) {
        continue;
      }
      
      await this.set(key, value, TraeAPI.ConfigurationTarget.Workspace);
    }
  }

  private async importExtensions(extensions: ExtensionInfo[]): Promise<void> {
    const installPromises = extensions.map(async (ext) => {
      try {
        // Check if extension is already installed
        const existing = TraeAPI.extensions.getExtension(ext.id);
        if (existing) {
          console.log(`Extension already installed: ${ext.id}`);
          return;
        }
        
        // Install extension (this would require extension management API)
        console.log(`Would install extension: ${ext.id}@${ext.version}`);
        // await TraeAPI.extensions.install(ext.id, ext.version);
      } catch (error) {
        console.error(`Failed to install extension ${ext.id}:`, error);
      }
    });
    
    await Promise.all(installPromises);
  }

  // Settings synchronization
  async syncSettings(syncOptions: SyncOptions = {}): Promise<boolean> {
    try {
      const {
        syncUserSettings = true,
        syncWorkspaceSettings = false,
        syncExtensions = true,
        syncKeybindings = true
      } = syncOptions;
      
      console.log('Starting settings synchronization...');
      
      if (syncUserSettings) {
        await this.syncUserSettings();
      }
      
      if (syncWorkspaceSettings) {
        await this.syncWorkspaceSettings();
      }
      
      if (syncExtensions) {
        await this.syncExtensions();
      }
      
      if (syncKeybindings) {
        await this.syncKeybindings();
      }
      
      console.log('Settings synchronization completed');
      return true;
    } catch (error) {
      console.error('Settings synchronization failed:', error);
      return false;
    }
  }

  private async syncUserSettings(): Promise<void> {
    // Implement user settings synchronization
    console.log('Syncing user settings...');
  }

  private async syncWorkspaceSettings(): Promise<void> {
    // Implement workspace settings synchronization
    console.log('Syncing workspace settings...');
  }

  private async syncExtensions(): Promise<void> {
    // Implement extensions synchronization
    console.log('Syncing extensions...');
  }

  private async syncKeybindings(): Promise<void> {
    // Implement keybindings synchronization
    console.log('Syncing keybindings...');
  }

  // Event handling
  onDidChangeConfiguration(listener: (event: TraeAPI.ConfigurationChangeEvent) => void): TraeAPI.Disposable {
    return TraeAPI.workspace.onDidChangeConfiguration(listener);
  }

  onDidChangeSetting<T>(key: string, listener: (value: T) => void): TraeAPI.Disposable {
    if (!this.settingsListeners.has(key)) {
      this.settingsListeners.set(key, []);
    }
    
    this.settingsListeners.get(key)!.push(listener);
    
    return {
      dispose: () => {
        const listeners = this.settingsListeners.get(key);
        if (listeners) {
          const index = listeners.indexOf(listener);
          if (index >= 0) {
            listeners.splice(index, 1);
          }
        }
      }
    };
  }

  private notifySettingChange(key: string, value: any): void {
    const listeners = this.settingsListeners.get(key);
    if (listeners) {
      listeners.forEach(listener => {
        try {
          listener(value);
        } catch (error) {
          console.error(`Error in setting change listener for ${key}:`, error);
        }
      });
    }
  }

  private handleConfigurationChange(event: TraeAPI.ConfigurationChangeEvent): void {
    // Update cache for changed settings
    this.settingsCache.clear();
    
    // Validate changed settings
    this.validateSettings();
    
    console.log('Configuration changed, cache cleared and settings validated');
  }

  private handleWorkspaceFoldersChange(event: TraeAPI.WorkspaceFoldersChangeEvent): void {
    // Reload workspace-specific schemas
    this.loadCustomSchemas();
    
    console.log('Workspace folders changed, reloaded custom schemas');
  }

  // Utility methods
  getAllSettings(): { [key: string]: any } {
    const config = TraeAPI.workspace.getConfiguration();
    const allSettings: { [key: string]: any } = {};
    
    // Get all settings from all schemas
    for (const schema of this.customSchemas.values()) {
      Object.keys(schema.properties).forEach(key => {
        if (config.has(key)) {
          allSettings[key] = this.get(key);
        }
      });
    }
    
    return allSettings;
  }

  getUserSettings(): { [key: string]: any } {
    const config = TraeAPI.workspace.getConfiguration();
    const userSettings: { [key: string]: any } = {};
    
    for (const schema of this.customSchemas.values()) {
      Object.keys(schema.properties).forEach(key => {
        const inspection = config.inspect(key);
        if (inspection && inspection.globalValue !== undefined) {
          userSettings[key] = inspection.globalValue;
        }
      });
    }
    
    return userSettings;
  }

  getWorkspaceSettings(): { [key: string]: any } {
    const config = TraeAPI.workspace.getConfiguration();
    const workspaceSettings: { [key: string]: any } = {};
    
    for (const schema of this.customSchemas.values()) {
      Object.keys(schema.properties).forEach(key => {
        const inspection = config.inspect(key);
        if (inspection && (inspection.workspaceValue !== undefined || inspection.workspaceFolderValue !== undefined)) {
          workspaceSettings[key] = inspection.workspaceValue || inspection.workspaceFolderValue;
        }
      });
    }
    
    return workspaceSettings;
  }

  getInstalledExtensions(): ExtensionInfo[] {
    return TraeAPI.extensions.all.map(ext => ({
      id: ext.id,
      version: ext.packageJSON.version,
      name: ext.packageJSON.displayName || ext.packageJSON.name,
      description: ext.packageJSON.description || '',
      enabled: ext.isActive
    }));
  }

  getSchemas(): SettingsSchema[] {
    return Array.from(this.customSchemas.values());
  }

  getSchema(schemaId: string): SettingsSchema | undefined {
    return this.customSchemas.get(schemaId);
  }

  private isValidSchema(schemaData: any): boolean {
    return (
      schemaData &&
      typeof schemaData === 'object' &&
      schemaData.properties &&
      typeof schemaData.properties === 'object'
    );
  }

  dispose(): void {
    this.settingsCache.clear();
    this.settingsListeners.clear();
    this.customSchemas.clear();
    this.settingsValidators.clear();
  }
}

// Interfaces
interface SettingsSchema {
  id: string;
  title: string;
  description: string;
  properties: { [key: string]: PropertySchema };
  source: 'extension' | 'workspace' | 'builtin';
  extensionId?: string;
}

interface PropertySchema {
  type?: string;
  title?: string;
  description?: string;
  default?: any;
  enum?: any[];
  minimum?: number;
  maximum?: number;
  minLength?: number;
  maxLength?: number;
  pattern?: string;
  items?: {
    type?: string;
  };
  minItems?: number;
  maxItems?: number;
}

interface SettingsValidator {
  schema: SettingsSchema;
  validate: (key: string, value: any) => ValidationResult;
}

interface ValidationResult {
  isValid: boolean;
  errors?: string[];
}

interface SettingInspection<T> {
  key: string;
  defaultValue?: T;
  globalValue?: T;
  workspaceValue?: T;
  workspaceFolderValue?: T;
  effectiveValue?: T;
  source: SettingSource;
}

type SettingSource = 'default' | 'user' | 'workspace' | 'workspaceFolder';

interface SettingSearchResult {
  key: string;
  title: string;
  description: string;
  type: string;
  currentValue: any;
  defaultValue: any;
  schema: string;
  source: string;
}

interface SettingsExport {
  version: string;
  exportedAt: string;
  userSettings: { [key: string]: any };
  workspaceSettings: { [key: string]: any };
  extensions?: ExtensionInfo[];
}

interface ExtensionInfo {
  id: string;
  version: string;
  name: string;
  description: string;
  enabled: boolean;
}

interface ImportOptions {
  includeUserSettings?: boolean;
  includeWorkspaceSettings?: boolean;
  includeExtensions?: boolean;
  overwriteExisting?: boolean;
}

interface SyncOptions {
  syncUserSettings?: boolean;
  syncWorkspaceSettings?: boolean;
  syncExtensions?: boolean;
  syncKeybindings?: boolean;
}

// Initialize settings manager
const settingsManager = new SettingsManager();

Settings UI Components

typescript
// Settings editor component
class SettingsEditor {
  private container: HTMLElement;
  private searchInput: HTMLInputElement;
  private categoryFilter: HTMLSelectElement;
  private settingsList: HTMLElement;
  private currentSettings: SettingSearchResult[] = [];
  private filteredSettings: SettingSearchResult[] = [];

  constructor(container: HTMLElement) {
    this.container = container;
    this.createUI();
    this.loadSettings();
    this.setupEventListeners();
  }

  private createUI(): void {
    this.container.innerHTML = `
      <div class="settings-editor">
        <div class="settings-header">
          <div class="settings-search">
            <input type="text" id="settings-search" placeholder="Search settings..." />
            <select id="category-filter">
              <option value="">All Categories</option>
              <option value="editor">Editor</option>
              <option value="workbench">Workbench</option>
              <option value="files">Files</option>
              <option value="terminal">Terminal</option>
              <option value="extensions">Extensions</option>
              <option value="ai">AI</option>
            </select>
          </div>
          <div class="settings-actions">
            <button id="export-settings">Export</button>
            <button id="import-settings">Import</button>
            <button id="reset-settings">Reset All</button>
          </div>
        </div>
        <div class="settings-content">
          <div id="settings-list" class="settings-list"></div>
        </div>
      </div>
    `;

    this.searchInput = this.container.querySelector('#settings-search') as HTMLInputElement;
    this.categoryFilter = this.container.querySelector('#category-filter') as HTMLSelectElement;
    this.settingsList = this.container.querySelector('#settings-list') as HTMLElement;
  }

  private setupEventListeners(): void {
    // Search input
    this.searchInput.addEventListener('input', () => {
      this.filterSettings();
    });

    // Category filter
    this.categoryFilter.addEventListener('change', () => {
      this.filterSettings();
    });

    // Action buttons
    this.container.querySelector('#export-settings')?.addEventListener('click', () => {
      this.exportSettings();
    });

    this.container.querySelector('#import-settings')?.addEventListener('click', () => {
      this.importSettings();
    });

    this.container.querySelector('#reset-settings')?.addEventListener('click', () => {
      this.resetAllSettings();
    });
  }

  private async loadSettings(): Promise<void> {
    try {
      // Load all settings
      this.currentSettings = settingsManager.searchSettings('');
      this.filteredSettings = [...this.currentSettings];
      this.renderSettings();
    } catch (error) {
      console.error('Failed to load settings:', error);
    }
  }

  private filterSettings(): void {
    const query = this.searchInput.value.toLowerCase();
    const category = this.categoryFilter.value;

    this.filteredSettings = this.currentSettings.filter(setting => {
      const matchesQuery = !query || 
        setting.key.toLowerCase().includes(query) ||
        setting.title.toLowerCase().includes(query) ||
        setting.description.toLowerCase().includes(query);

      const matchesCategory = !category || setting.key.startsWith(category + '.');

      return matchesQuery && matchesCategory;
    });

    this.renderSettings();
  }

  private renderSettings(): void {
    this.settingsList.innerHTML = '';

    if (this.filteredSettings.length === 0) {
      this.settingsList.innerHTML = '<div class="no-settings">No settings found</div>';
      return;
    }

    this.filteredSettings.forEach(setting => {
      const settingElement = this.createSettingElement(setting);
      this.settingsList.appendChild(settingElement);
    });
  }

  private createSettingElement(setting: SettingSearchResult): HTMLElement {
    const element = document.createElement('div');
    element.className = 'setting-item';
    element.innerHTML = `
      <div class="setting-header">
        <div class="setting-title">${setting.title}</div>
        <div class="setting-key">${setting.key}</div>
      </div>
      <div class="setting-description">${setting.description}</div>
      <div class="setting-control">
        ${this.createSettingControl(setting)}
      </div>
      <div class="setting-actions">
        <button class="reset-button" data-key="${setting.key}">Reset</button>
      </div>
    `;

    // Add event listeners
    const resetButton = element.querySelector('.reset-button') as HTMLButtonElement;
    resetButton.addEventListener('click', () => {
      this.resetSetting(setting.key);
    });

    return element;
  }

  private createSettingControl(setting: SettingSearchResult): string {
    switch (setting.type) {
      case 'boolean':
        return `<input type="checkbox" ${setting.currentValue ? 'checked' : ''} data-key="${setting.key}" />`;
      
      case 'number':
        return `<input type="number" value="${setting.currentValue || ''}" data-key="${setting.key}" />`;
      
      case 'string':
        return `<input type="text" value="${setting.currentValue || ''}" data-key="${setting.key}" />`;
      
      case 'array':
        return `<textarea data-key="${setting.key}">${JSON.stringify(setting.currentValue || [], null, 2)}</textarea>`;
      
      case 'object':
        return `<textarea data-key="${setting.key}">${JSON.stringify(setting.currentValue || {}, null, 2)}</textarea>`;
      
      default:
        return `<input type="text" value="${setting.currentValue || ''}" data-key="${setting.key}" />`;
    }
  }

  private async resetSetting(key: string): Promise<void> {
    try {
      await settingsManager.reset(key);
      await this.loadSettings();
      TraeAPI.window.showInformationMessage(`Setting "${key}" reset to default`);
    } catch (error) {
      console.error(`Failed to reset setting ${key}:`, error);
      TraeAPI.window.showErrorMessage(`Failed to reset setting: ${error.message}`);
    }
  }

  private async exportSettings(): Promise<void> {
    try {
      const settings = await settingsManager.exportSettings(true);
      const blob = new Blob([JSON.stringify(settings, null, 2)], { type: 'application/json' });
      const url = URL.createObjectURL(blob);
      
      const a = document.createElement('a');
      a.href = url;
      a.download = `trae-settings-${new Date().toISOString().split('T')[0]}.json`;
      a.click();
      
      URL.revokeObjectURL(url);
      TraeAPI.window.showInformationMessage('Settings exported successfully');
    } catch (error) {
      console.error('Failed to export settings:', error);
      TraeAPI.window.showErrorMessage(`Failed to export settings: ${error.message}`);
    }
  }

  private async importSettings(): Promise<void> {
    try {
      const input = document.createElement('input');
      input.type = 'file';
      input.accept = '.json';
      
      input.onchange = async (event) => {
        const file = (event.target as HTMLInputElement).files?.[0];
        if (!file) return;
        
        try {
          const text = await file.text();
          const settings = JSON.parse(text);
          
          const success = await settingsManager.importSettings(settings, {
            includeUserSettings: true,
            includeWorkspaceSettings: true,
            includeExtensions: false,
            overwriteExisting: false
          });
          
          if (success) {
            await this.loadSettings();
          }
        } catch (error) {
          console.error('Failed to import settings:', error);
          TraeAPI.window.showErrorMessage(`Failed to import settings: ${error.message}`);
        }
      };
      
      input.click();
    } catch (error) {
      console.error('Failed to import settings:', error);
    }
  }

  private async resetAllSettings(): Promise<void> {
    const confirmed = await TraeAPI.window.showWarningMessage(
      'Are you sure you want to reset all settings to their defaults? This action cannot be undone.',
      { modal: true },
      'Reset All Settings'
    );
    
    if (confirmed === 'Reset All Settings') {
      try {
        const modifiedSettings = settingsManager.getModifiedSettings();
        
        for (const setting of modifiedSettings) {
          await settingsManager.reset(setting.key);
        }
        
        await this.loadSettings();
        TraeAPI.window.showInformationMessage(`Reset ${modifiedSettings.length} settings to defaults`);
      } catch (error) {
        console.error('Failed to reset all settings:', error);
        TraeAPI.window.showErrorMessage(`Failed to reset settings: ${error.message}`);
      }
    }
  }

  dispose(): void {
    // Clean up event listeners and resources
  }
}

API Reference

Core Interfaces

typescript
interface SettingsAPI {
  // Basic operations
  get<T>(key: string, defaultValue?: T): T;
  set<T>(key: string, value: T, target?: ConfigurationTarget): Promise<boolean>;
  update<T>(key: string, value: T, target?: ConfigurationTarget): Promise<boolean>;
  reset(key: string, target?: ConfigurationTarget): Promise<boolean>;
  has(key: string): boolean;
  inspect<T>(key: string): SettingInspection<T> | undefined;
  
  // Search and filtering
  searchSettings(query: string): SettingSearchResult[];
  getSettingsByCategory(category: string): SettingSearchResult[];
  getModifiedSettings(): SettingSearchResult[];
  
  // Validation
  validateSetting(key: string, value: any): ValidationResult;
  validateAllSettings(): { [key: string]: ValidationResult };
  
  // Import/Export
  exportSettings(includeExtensions?: boolean): Promise<SettingsExport>;
  importSettings(settingsData: SettingsExport, options?: ImportOptions): Promise<boolean>;
  
  // Synchronization
  syncSettings(options?: SyncOptions): Promise<boolean>;
  
  // Events
  onDidChangeConfiguration(listener: (event: ConfigurationChangeEvent) => void): Disposable;
  onDidChangeSetting<T>(key: string, listener: (value: T) => void): Disposable;
  
  // Schemas
  getSchemas(): SettingsSchema[];
  getSchema(schemaId: string): SettingsSchema | undefined;
}

Best Practices

  1. Setting Keys: Use hierarchical naming with dots (e.g., editor.fontSize)
  2. Validation: Always validate setting values before applying
  3. Default Values: Provide sensible defaults for all settings
  4. Documentation: Document all custom settings with clear descriptions
  5. Performance: Cache frequently accessed settings
  6. User Experience: Provide clear error messages for invalid settings
  7. Backward Compatibility: Handle setting migrations gracefully
  8. Security: Validate and sanitize all setting values

Your Ultimate AI-Powered IDE Learning Guide