Skip to content

Settings API

Settings APIは、開発環境内でアプリケーション設定、ユーザー設定、設定オプションを管理するための包括的な機能を提供します。

概要

Settings APIでは以下のことができます:

  • ユーザーとワークスペース設定の読み書き
  • カスタム設定スキーマの定義
  • 設定の検証とマイグレーションの処理
  • 設定UIとエディターの提供
  • 設定の同期管理
  • 設定の継承とオーバーライドの処理
  • 設定のバックアップと復元の実装
  • 設定の検索とフィルタリングの提供

基本的な使用方法

設定管理

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

// 設定マネージャーの実装
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 {
    // デフォルト設定を初期化
    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
    };

    // デフォルト設定をキャッシュ
    Object.entries(defaultSettings).forEach(([key, value]) => {
      this.settingsCache.set(key, value);
    });

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

  private setupConfigurationListeners(): void {
    // 設定変更をリッスン
    TraeAPI.workspace.onDidChangeConfiguration(event => {
      this.handleConfigurationChange(event);
    });

    // ワークスペースフォルダー変更をリッスン
    TraeAPI.workspace.onDidChangeWorkspaceFolders(event => {
      this.handleWorkspaceFoldersChange(event);
    });
  }

  private async loadCustomSchemas(): Promise<void> {
    try {
      // 拡張機能とワークスペースからカスタム設定スキーマを読み込み
      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;
}

ベストプラクティス

  1. 設定キー: ドット記法を使用した階層的な命名(例:editor.fontSize
  2. 検証: 設定値を適用する前に常に検証を行う
  3. デフォルト値: すべての設定に適切なデフォルト値を提供する
  4. ドキュメント: カスタム設定には明確な説明を記載する
  5. パフォーマンス: 頻繁にアクセスされる設定をキャッシュする
  6. ユーザーエクスペリエンス: 無効な設定に対して明確なエラーメッセージを提供する
  7. 後方互換性: 設定の移行を適切に処理する
  8. セキュリティ: すべての設定値を検証およびサニタイズする

関連API

究極の AI 駆動 IDE 学習ガイド