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
- Setting Keys: Use hierarchical naming with dots (e.g.,
editor.fontSize) - Validation: Always validate setting values before applying
- Default Values: Provide sensible defaults for all settings
- Documentation: Document all custom settings with clear descriptions
- Performance: Cache frequently accessed settings
- User Experience: Provide clear error messages for invalid settings
- Backward Compatibility: Handle setting migrations gracefully
- Security: Validate and sanitize all setting values
Related APIs
- Workspace API - For workspace-specific settings
- Extensions API - For extension settings
- UI API - For settings UI components
- Commands API - For settings-related commands