Themes API
The Themes API provides comprehensive functionality for creating, managing, and applying custom themes within the development environment.
Overview
The Themes API enables you to:
- Create custom color themes and syntax highlighting
- Manage theme switching and preferences
- Provide theme customization options
- Handle light/dark mode transitions
- Create icon themes and file associations
- Implement theme inheritance and variants
- Provide theme preview and testing capabilities
- Handle theme packaging and distribution
Basic Usage
Theme Management
typescript
import { TraeAPI } from '@trae/api';
// Theme manager implementation
class ThemeManager {
private themes: Map<string, ThemeInfo> = new Map();
private currentTheme: string | null = null;
private themeChangeListeners: ((theme: ThemeInfo) => void)[] = [];
private customThemes: Map<string, CustomTheme> = new Map();
constructor() {
this.loadBuiltInThemes();
this.loadCustomThemes();
this.setupThemeListeners();
this.applyStoredTheme();
}
private loadBuiltInThemes(): void {
// Load built-in themes
const builtInThemes: ThemeInfo[] = [
{
id: 'dark-default',
label: 'Dark (Default)',
description: 'Default dark theme',
type: 'dark',
isBuiltIn: true,
colors: {
'editor.background': '#1e1e1e',
'editor.foreground': '#d4d4d4',
'activityBar.background': '#333333',
'sideBar.background': '#252526',
'statusBar.background': '#007acc',
'titleBar.activeBackground': '#3c3c3c'
},
tokenColors: [
{
scope: 'comment',
settings: {
foreground: '#6A9955',
fontStyle: 'italic'
}
},
{
scope: 'keyword',
settings: {
foreground: '#569CD6'
}
},
{
scope: 'string',
settings: {
foreground: '#CE9178'
}
}
]
},
{
id: 'light-default',
label: 'Light (Default)',
description: 'Default light theme',
type: 'light',
isBuiltIn: true,
colors: {
'editor.background': '#ffffff',
'editor.foreground': '#000000',
'activityBar.background': '#f3f3f3',
'sideBar.background': '#f8f8f8',
'statusBar.background': '#007acc',
'titleBar.activeBackground': '#dddddd'
},
tokenColors: [
{
scope: 'comment',
settings: {
foreground: '#008000',
fontStyle: 'italic'
}
},
{
scope: 'keyword',
settings: {
foreground: '#0000ff'
}
},
{
scope: 'string',
settings: {
foreground: '#a31515'
}
}
]
},
{
id: 'high-contrast',
label: 'High Contrast',
description: 'High contrast theme for accessibility',
type: 'hc',
isBuiltIn: true,
colors: {
'editor.background': '#000000',
'editor.foreground': '#ffffff',
'activityBar.background': '#000000',
'sideBar.background': '#000000',
'statusBar.background': '#000000',
'titleBar.activeBackground': '#000000'
},
tokenColors: [
{
scope: 'comment',
settings: {
foreground: '#7ca668'
}
},
{
scope: 'keyword',
settings: {
foreground: '#569cd6'
}
},
{
scope: 'string',
settings: {
foreground: '#ce9178'
}
}
]
}
];
builtInThemes.forEach(theme => {
this.themes.set(theme.id, theme);
});
console.log(`Loaded ${builtInThemes.length} built-in themes`);
}
private async loadCustomThemes(): Promise<void> {
try {
// Load custom themes from workspace and global storage
const workspaceThemes = await this.loadWorkspaceThemes();
const globalThemes = await this.loadGlobalThemes();
[...workspaceThemes, ...globalThemes].forEach(theme => {
this.themes.set(theme.id, theme);
});
console.log(`Loaded ${workspaceThemes.length + globalThemes.length} custom themes`);
} catch (error) {
console.error('Failed to load custom themes:', error);
}
}
private async loadWorkspaceThemes(): Promise<ThemeInfo[]> {
const themes: ThemeInfo[] = [];
if (!TraeAPI.workspace.workspaceFolders) {
return themes;
}
for (const folder of TraeAPI.workspace.workspaceFolders) {
const themeFiles = await TraeAPI.workspace.findFiles(
new TraeAPI.RelativePattern(folder, '**/*.json'),
'**/node_modules/**'
);
for (const file of themeFiles) {
if (file.fsPath.includes('themes') || file.fsPath.endsWith('-theme.json')) {
try {
const content = await TraeAPI.workspace.fs.readFile(file);
const themeData = JSON.parse(content.toString());
if (this.isValidTheme(themeData)) {
const theme = this.parseThemeFile(file, themeData);
themes.push(theme);
}
} catch (error) {
console.warn(`Failed to load theme file ${file.fsPath}:`, error);
}
}
}
}
return themes;
}
private async loadGlobalThemes(): Promise<ThemeInfo[]> {
// Load themes from global storage or user settings
const themes: ThemeInfo[] = [];
try {
const globalThemeData = TraeAPI.workspace.getConfiguration('themes').get('custom', []);
for (const themeData of globalThemeData) {
if (this.isValidTheme(themeData)) {
const theme: ThemeInfo = {
id: themeData.id || `custom-${Date.now()}`,
label: themeData.label || 'Custom Theme',
description: themeData.description || '',
type: themeData.type || 'dark',
isBuiltIn: false,
colors: themeData.colors || {},
tokenColors: themeData.tokenColors || []
};
themes.push(theme);
}
}
} catch (error) {
console.warn('Failed to load global themes:', error);
}
return themes;
}
private setupThemeListeners(): void {
// Listen for configuration changes
TraeAPI.workspace.onDidChangeConfiguration(event => {
if (event.affectsConfiguration('workbench.colorTheme') ||
event.affectsConfiguration('themes')) {
this.handleConfigurationChange();
}
});
// Listen for file system changes for theme files
const themeWatcher = TraeAPI.workspace.createFileSystemWatcher('**/*theme*.json');
themeWatcher.onDidCreate(uri => this.handleThemeFileChange(uri));
themeWatcher.onDidChange(uri => this.handleThemeFileChange(uri));
themeWatcher.onDidDelete(uri => this.handleThemeFileDelete(uri));
}
private async applyStoredTheme(): Promise<void> {
const storedTheme = TraeAPI.workspace.getConfiguration('workbench').get('colorTheme');
if (storedTheme && this.themes.has(storedTheme as string)) {
await this.applyTheme(storedTheme as string);
} else {
// Apply default theme based on system preference
const prefersDark = await this.getSystemThemePreference();
const defaultTheme = prefersDark ? 'dark-default' : 'light-default';
await this.applyTheme(defaultTheme);
}
}
private async getSystemThemePreference(): Promise<boolean> {
// Check system theme preference
try {
if (typeof window !== 'undefined' && window.matchMedia) {
return window.matchMedia('(prefers-color-scheme: dark)').matches;
}
} catch (error) {
console.warn('Failed to detect system theme preference:', error);
}
return true; // Default to dark
}
// Apply theme
async applyTheme(themeId: string): Promise<boolean> {
const theme = this.themes.get(themeId);
if (!theme) {
console.error(`Theme not found: ${themeId}`);
return false;
}
try {
console.log(`Applying theme: ${theme.label}`);
// Apply color theme
await this.applyColorTheme(theme);
// Apply syntax highlighting
await this.applySyntaxHighlighting(theme);
// Update current theme
this.currentTheme = themeId;
// Save to configuration
await TraeAPI.workspace.getConfiguration('workbench').update(
'colorTheme',
themeId,
TraeAPI.ConfigurationTarget.Global
);
// Notify listeners
this.notifyThemeChange(theme);
console.log(`Theme applied successfully: ${theme.label}`);
return true;
} catch (error) {
console.error(`Failed to apply theme ${themeId}:`, error);
TraeAPI.window.showErrorMessage(`Failed to apply theme: ${error.message}`);
return false;
}
}
private async applyColorTheme(theme: ThemeInfo): Promise<void> {
// Apply color theme to the editor and UI
const colors = theme.colors;
// Apply colors to CSS custom properties or theme system
if (typeof document !== 'undefined') {
const root = document.documentElement;
Object.entries(colors).forEach(([key, value]) => {
const cssVar = `--theme-${key.replace(/\./g, '-')}`;
root.style.setProperty(cssVar, value);
});
// Set theme type class
root.className = root.className.replace(/theme-(light|dark|hc)/g, '');
root.classList.add(`theme-${theme.type}`);
}
}
private async applySyntaxHighlighting(theme: ThemeInfo): Promise<void> {
// Apply syntax highlighting rules
const tokenColors = theme.tokenColors;
// Generate CSS for syntax highlighting
const syntaxCSS = this.generateSyntaxCSS(tokenColors);
// Apply syntax CSS
if (typeof document !== 'undefined') {
let syntaxStyleElement = document.getElementById('theme-syntax-highlighting');
if (!syntaxStyleElement) {
syntaxStyleElement = document.createElement('style');
syntaxStyleElement.id = 'theme-syntax-highlighting';
document.head.appendChild(syntaxStyleElement);
}
syntaxStyleElement.textContent = syntaxCSS;
}
}
private generateSyntaxCSS(tokenColors: TokenColor[]): string {
let css = '';
tokenColors.forEach((tokenColor, index) => {
const scopes = Array.isArray(tokenColor.scope) ? tokenColor.scope : [tokenColor.scope];
scopes.forEach(scope => {
const selector = `.token.${scope.replace(/\./g, '-')}`;
const styles: string[] = [];
if (tokenColor.settings.foreground) {
styles.push(`color: ${tokenColor.settings.foreground}`);
}
if (tokenColor.settings.background) {
styles.push(`background-color: ${tokenColor.settings.background}`);
}
if (tokenColor.settings.fontStyle) {
if (tokenColor.settings.fontStyle.includes('italic')) {
styles.push('font-style: italic');
}
if (tokenColor.settings.fontStyle.includes('bold')) {
styles.push('font-weight: bold');
}
if (tokenColor.settings.fontStyle.includes('underline')) {
styles.push('text-decoration: underline');
}
}
if (styles.length > 0) {
css += `${selector} { ${styles.join('; ')} }\n`;
}
});
});
return css;
}
// Theme creation and customization
async createCustomTheme(options: ThemeCreationOptions): Promise<string> {
const themeId = `custom-${options.name.toLowerCase().replace(/\s+/g, '-')}-${Date.now()}`;
const customTheme: CustomTheme = {
id: themeId,
name: options.name,
description: options.description || '',
baseTheme: options.baseTheme || 'dark-default',
customizations: options.customizations || {},
author: options.author || 'User',
version: '1.0.0',
created: new Date(),
modified: new Date()
};
// Generate theme from base theme and customizations
const theme = await this.generateThemeFromCustomization(customTheme);
// Store custom theme
this.customThemes.set(themeId, customTheme);
this.themes.set(themeId, theme);
// Save to storage
await this.saveCustomTheme(customTheme);
console.log(`Custom theme created: ${options.name}`);
TraeAPI.window.showInformationMessage(`Custom theme "${options.name}" created successfully!`);
return themeId;
}
private async generateThemeFromCustomization(customTheme: CustomTheme): Promise<ThemeInfo> {
const baseTheme = this.themes.get(customTheme.baseTheme);
if (!baseTheme) {
throw new Error(`Base theme not found: ${customTheme.baseTheme}`);
}
// Clone base theme
const theme: ThemeInfo = {
id: customTheme.id,
label: customTheme.name,
description: customTheme.description,
type: baseTheme.type,
isBuiltIn: false,
colors: { ...baseTheme.colors },
tokenColors: [...baseTheme.tokenColors]
};
// Apply customizations
const customizations = customTheme.customizations;
// Apply color customizations
if (customizations.colors) {
Object.assign(theme.colors, customizations.colors);
}
// Apply token color customizations
if (customizations.tokenColors) {
// Merge or replace token colors
customizations.tokenColors.forEach(customToken => {
const existingIndex = theme.tokenColors.findIndex(token =>
token.scope === customToken.scope
);
if (existingIndex >= 0) {
// Update existing token color
theme.tokenColors[existingIndex] = { ...theme.tokenColors[existingIndex], ...customToken };
} else {
// Add new token color
theme.tokenColors.push(customToken);
}
});
}
// Apply semantic highlighting customizations
if (customizations.semanticHighlighting) {
theme.semanticHighlighting = customizations.semanticHighlighting;
}
return theme;
}
async updateCustomTheme(themeId: string, updates: Partial<ThemeCreationOptions>): Promise<boolean> {
const customTheme = this.customThemes.get(themeId);
if (!customTheme) {
console.error(`Custom theme not found: ${themeId}`);
return false;
}
try {
// Update custom theme
if (updates.name) customTheme.name = updates.name;
if (updates.description) customTheme.description = updates.description;
if (updates.customizations) {
customTheme.customizations = { ...customTheme.customizations, ...updates.customizations };
}
customTheme.modified = new Date();
// Regenerate theme
const theme = await this.generateThemeFromCustomization(customTheme);
this.themes.set(themeId, theme);
// Save to storage
await this.saveCustomTheme(customTheme);
// Reapply if current theme
if (this.currentTheme === themeId) {
await this.applyTheme(themeId);
}
console.log(`Custom theme updated: ${customTheme.name}`);
return true;
} catch (error) {
console.error(`Failed to update custom theme ${themeId}:`, error);
return false;
}
}
async deleteCustomTheme(themeId: string): Promise<boolean> {
const customTheme = this.customThemes.get(themeId);
if (!customTheme) {
console.error(`Custom theme not found: ${themeId}`);
return false;
}
try {
// Switch to default theme if deleting current theme
if (this.currentTheme === themeId) {
await this.applyTheme('dark-default');
}
// Remove from storage
await this.removeCustomTheme(themeId);
// Remove from memory
this.customThemes.delete(themeId);
this.themes.delete(themeId);
console.log(`Custom theme deleted: ${customTheme.name}`);
TraeAPI.window.showInformationMessage(`Custom theme "${customTheme.name}" deleted successfully!`);
return true;
} catch (error) {
console.error(`Failed to delete custom theme ${themeId}:`, error);
return false;
}
}
// Theme import/export
async exportTheme(themeId: string): Promise<string | undefined> {
const theme = this.themes.get(themeId);
if (!theme) {
console.error(`Theme not found: ${themeId}`);
return undefined;
}
try {
const exportData = {
name: theme.label,
type: theme.type,
colors: theme.colors,
tokenColors: theme.tokenColors,
semanticHighlighting: theme.semanticHighlighting,
metadata: {
id: theme.id,
description: theme.description,
exportedAt: new Date().toISOString(),
version: '1.0.0'
}
};
const exportJson = JSON.stringify(exportData, null, 2);
// Save to file
const fileName = `${theme.label.replace(/\s+/g, '-').toLowerCase()}-theme.json`;
const uri = await TraeAPI.window.showSaveDialog({
defaultUri: TraeAPI.Uri.file(fileName),
filters: {
'Theme Files': ['json']
}
});
if (uri) {
await TraeAPI.workspace.fs.writeFile(uri, Buffer.from(exportJson));
TraeAPI.window.showInformationMessage(`Theme exported to ${uri.fsPath}`);
return uri.fsPath;
}
return undefined;
} catch (error) {
console.error(`Failed to export theme ${themeId}:`, error);
TraeAPI.window.showErrorMessage(`Failed to export theme: ${error.message}`);
return undefined;
}
}
async importTheme(filePath?: string): Promise<string | undefined> {
try {
let uri: TraeAPI.Uri;
if (filePath) {
uri = TraeAPI.Uri.file(filePath);
} else {
const uris = await TraeAPI.window.showOpenDialog({
canSelectFiles: true,
canSelectMany: false,
filters: {
'Theme Files': ['json']
}
});
if (!uris || uris.length === 0) {
return undefined;
}
uri = uris[0];
}
// Read theme file
const content = await TraeAPI.workspace.fs.readFile(uri);
const themeData = JSON.parse(content.toString());
if (!this.isValidTheme(themeData)) {
throw new Error('Invalid theme file format');
}
// Create theme
const themeId = `imported-${themeData.name.toLowerCase().replace(/\s+/g, '-')}-${Date.now()}`;
const theme: ThemeInfo = {
id: themeId,
label: themeData.name,
description: themeData.description || themeData.metadata?.description || '',
type: themeData.type || 'dark',
isBuiltIn: false,
colors: themeData.colors || {},
tokenColors: themeData.tokenColors || [],
semanticHighlighting: themeData.semanticHighlighting
};
// Add to themes
this.themes.set(themeId, theme);
// Save as custom theme
const customTheme: CustomTheme = {
id: themeId,
name: theme.label,
description: theme.description,
baseTheme: 'dark-default',
customizations: {
colors: theme.colors,
tokenColors: theme.tokenColors,
semanticHighlighting: theme.semanticHighlighting
},
author: themeData.metadata?.author || 'Unknown',
version: themeData.metadata?.version || '1.0.0',
created: new Date(),
modified: new Date()
};
this.customThemes.set(themeId, customTheme);
await this.saveCustomTheme(customTheme);
console.log(`Theme imported: ${theme.label}`);
TraeAPI.window.showInformationMessage(`Theme "${theme.label}" imported successfully!`);
return themeId;
} catch (error) {
console.error('Failed to import theme:', error);
TraeAPI.window.showErrorMessage(`Failed to import theme: ${error.message}`);
return undefined;
}
}
// Theme utilities
getThemes(): ThemeInfo[] {
return Array.from(this.themes.values());
}
getTheme(themeId: string): ThemeInfo | undefined {
return this.themes.get(themeId);
}
getCurrentTheme(): ThemeInfo | undefined {
return this.currentTheme ? this.themes.get(this.currentTheme) : undefined;
}
getThemesByType(type: 'light' | 'dark' | 'hc'): ThemeInfo[] {
return Array.from(this.themes.values()).filter(theme => theme.type === type);
}
getCustomThemes(): CustomTheme[] {
return Array.from(this.customThemes.values());
}
searchThemes(query: string): ThemeInfo[] {
const lowercaseQuery = query.toLowerCase();
return Array.from(this.themes.values()).filter(theme =>
theme.label.toLowerCase().includes(lowercaseQuery) ||
theme.description.toLowerCase().includes(lowercaseQuery)
);
}
// Theme preview
async previewTheme(themeId: string): Promise<boolean> {
const theme = this.themes.get(themeId);
if (!theme) {
console.error(`Theme not found: ${themeId}`);
return false;
}
try {
// Store current theme for restoration
const previousTheme = this.currentTheme;
// Apply preview theme
await this.applyTheme(themeId);
// Show preview dialog
const action = await TraeAPI.window.showInformationMessage(
`Previewing theme: ${theme.label}`,
{ modal: true },
'Apply Theme',
'Restore Previous'
);
if (action === 'Apply Theme') {
// Keep the theme
return true;
} else {
// Restore previous theme
if (previousTheme) {
await this.applyTheme(previousTheme);
}
return false;
}
} catch (error) {
console.error(`Failed to preview theme ${themeId}:`, error);
return false;
}
}
// Event handling
onThemeChange(listener: (theme: ThemeInfo) => void): TraeAPI.Disposable {
this.themeChangeListeners.push(listener);
return {
dispose: () => {
const index = this.themeChangeListeners.indexOf(listener);
if (index >= 0) {
this.themeChangeListeners.splice(index, 1);
}
}
};
}
private notifyThemeChange(theme: ThemeInfo): void {
this.themeChangeListeners.forEach(listener => {
try {
listener(theme);
} catch (error) {
console.error('Error in theme change listener:', error);
}
});
}
private async handleConfigurationChange(): Promise<void> {
const currentConfigTheme = TraeAPI.workspace.getConfiguration('workbench').get('colorTheme');
if (currentConfigTheme && currentConfigTheme !== this.currentTheme) {
await this.applyTheme(currentConfigTheme as string);
}
}
private async handleThemeFileChange(uri: TraeAPI.Uri): Promise<void> {
console.log(`Theme file changed: ${uri.fsPath}`);
// Reload themes
await this.loadCustomThemes();
}
private async handleThemeFileDelete(uri: TraeAPI.Uri): Promise<void> {
console.log(`Theme file deleted: ${uri.fsPath}`);
// Find and remove theme
const themeToRemove = Array.from(this.themes.values()).find(theme =>
!theme.isBuiltIn && uri.fsPath.includes(theme.id)
);
if (themeToRemove) {
this.themes.delete(themeToRemove.id);
this.customThemes.delete(themeToRemove.id);
// Switch to default if current theme was deleted
if (this.currentTheme === themeToRemove.id) {
await this.applyTheme('dark-default');
}
}
}
// Storage methods
private async saveCustomTheme(customTheme: CustomTheme): Promise<void> {
try {
const customThemes = TraeAPI.workspace.getConfiguration('themes').get('custom', []);
const existingIndex = customThemes.findIndex((t: any) => t.id === customTheme.id);
if (existingIndex >= 0) {
customThemes[existingIndex] = customTheme;
} else {
customThemes.push(customTheme);
}
await TraeAPI.workspace.getConfiguration('themes').update(
'custom',
customThemes,
TraeAPI.ConfigurationTarget.Global
);
} catch (error) {
console.error('Failed to save custom theme:', error);
}
}
private async removeCustomTheme(themeId: string): Promise<void> {
try {
const customThemes = TraeAPI.workspace.getConfiguration('themes').get('custom', []);
const filteredThemes = customThemes.filter((t: any) => t.id !== themeId);
await TraeAPI.workspace.getConfiguration('themes').update(
'custom',
filteredThemes,
TraeAPI.ConfigurationTarget.Global
);
} catch (error) {
console.error('Failed to remove custom theme:', error);
}
}
// Validation
private isValidTheme(themeData: any): boolean {
return (
themeData &&
typeof themeData === 'object' &&
(themeData.name || themeData.label) &&
(themeData.colors || themeData.tokenColors)
);
}
private parseThemeFile(file: TraeAPI.Uri, themeData: any): ThemeInfo {
const fileName = file.fsPath.split('/').pop() || '';
const themeId = `file-${fileName.replace(/\.json$/, '')}-${Date.now()}`;
return {
id: themeId,
label: themeData.name || themeData.label || fileName,
description: themeData.description || '',
type: themeData.type || 'dark',
isBuiltIn: false,
colors: themeData.colors || {},
tokenColors: themeData.tokenColors || [],
semanticHighlighting: themeData.semanticHighlighting
};
}
dispose(): void {
this.themes.clear();
this.customThemes.clear();
this.themeChangeListeners.length = 0;
this.currentTheme = null;
}
}
// Interfaces
interface ThemeInfo {
id: string;
label: string;
description: string;
type: 'light' | 'dark' | 'hc';
isBuiltIn: boolean;
colors: Record<string, string>;
tokenColors: TokenColor[];
semanticHighlighting?: Record<string, any>;
}
interface TokenColor {
scope: string | string[];
settings: {
foreground?: string;
background?: string;
fontStyle?: string;
};
}
interface CustomTheme {
id: string;
name: string;
description: string;
baseTheme: string;
customizations: {
colors?: Record<string, string>;
tokenColors?: TokenColor[];
semanticHighlighting?: Record<string, any>;
};
author: string;
version: string;
created: Date;
modified: Date;
}
interface ThemeCreationOptions {
name: string;
description?: string;
baseTheme?: string;
customizations?: {
colors?: Record<string, string>;
tokenColors?: TokenColor[];
semanticHighlighting?: Record<string, any>;
};
author?: string;
}
// Initialize theme manager
const themeManager = new ThemeManager();Icon Themes
typescript
// Icon theme management
class IconThemeManager {
private iconThemes: Map<string, IconThemeInfo> = new Map();
private currentIconTheme: string | null = null;
private iconThemeChangeListeners: ((theme: IconThemeInfo) => void)[] = [];
constructor() {
this.loadBuiltInIconThemes();
this.loadCustomIconThemes();
this.applyStoredIconTheme();
}
private loadBuiltInIconThemes(): void {
const builtInIconThemes: IconThemeInfo[] = [
{
id: 'default-icons',
label: 'Default Icons',
description: 'Default file and folder icons',
isBuiltIn: true,
iconDefinitions: {
'file': { iconPath: './icons/file.svg' },
'folder': { iconPath: './icons/folder.svg' },
'folder-open': { iconPath: './icons/folder-open.svg' }
},
fileExtensions: {
'js': 'javascript-icon',
'ts': 'typescript-icon',
'json': 'json-icon',
'md': 'markdown-icon'
},
fileNames: {
'package.json': 'npm-icon',
'README.md': 'readme-icon'
},
folderNames: {
'node_modules': 'node-modules-icon',
'src': 'source-icon',
'dist': 'dist-icon'
}
}
];
builtInIconThemes.forEach(theme => {
this.iconThemes.set(theme.id, theme);
});
}
private async loadCustomIconThemes(): Promise<void> {
// Load custom icon themes from workspace and settings
try {
const customIconThemes = TraeAPI.workspace.getConfiguration('iconThemes').get('custom', []);
customIconThemes.forEach((themeData: any) => {
if (this.isValidIconTheme(themeData)) {
const theme: IconThemeInfo = {
id: themeData.id,
label: themeData.label,
description: themeData.description || '',
isBuiltIn: false,
iconDefinitions: themeData.iconDefinitions || {},
fileExtensions: themeData.fileExtensions || {},
fileNames: themeData.fileNames || {},
folderNames: themeData.folderNames || {}
};
this.iconThemes.set(theme.id, theme);
}
});
} catch (error) {
console.error('Failed to load custom icon themes:', error);
}
}
private async applyStoredIconTheme(): Promise<void> {
const storedIconTheme = TraeAPI.workspace.getConfiguration('workbench').get('iconTheme');
if (storedIconTheme && this.iconThemes.has(storedIconTheme as string)) {
await this.applyIconTheme(storedIconTheme as string);
} else {
await this.applyIconTheme('default-icons');
}
}
async applyIconTheme(themeId: string): Promise<boolean> {
const theme = this.iconThemes.get(themeId);
if (!theme) {
console.error(`Icon theme not found: ${themeId}`);
return false;
}
try {
console.log(`Applying icon theme: ${theme.label}`);
// Apply icon theme CSS
await this.applyIconThemeCSS(theme);
// Update current icon theme
this.currentIconTheme = themeId;
// Save to configuration
await TraeAPI.workspace.getConfiguration('workbench').update(
'iconTheme',
themeId,
TraeAPI.ConfigurationTarget.Global
);
// Notify listeners
this.notifyIconThemeChange(theme);
console.log(`Icon theme applied successfully: ${theme.label}`);
return true;
} catch (error) {
console.error(`Failed to apply icon theme ${themeId}:`, error);
return false;
}
}
private async applyIconThemeCSS(theme: IconThemeInfo): Promise<void> {
// Generate CSS for icon theme
const iconCSS = this.generateIconCSS(theme);
// Apply icon CSS
if (typeof document !== 'undefined') {
let iconStyleElement = document.getElementById('icon-theme-styles');
if (!iconStyleElement) {
iconStyleElement = document.createElement('style');
iconStyleElement.id = 'icon-theme-styles';
document.head.appendChild(iconStyleElement);
}
iconStyleElement.textContent = iconCSS;
}
}
private generateIconCSS(theme: IconThemeInfo): string {
let css = '';
// File extension icons
Object.entries(theme.fileExtensions).forEach(([extension, iconId]) => {
const iconDef = theme.iconDefinitions[iconId];
if (iconDef) {
css += `.file-icon[data-extension="${extension}"] { background-image: url(${iconDef.iconPath}); }\n`;
}
});
// File name icons
Object.entries(theme.fileNames).forEach(([fileName, iconId]) => {
const iconDef = theme.iconDefinitions[iconId];
if (iconDef) {
css += `.file-icon[data-filename="${fileName}"] { background-image: url(${iconDef.iconPath}); }\n`;
}
});
// Folder name icons
Object.entries(theme.folderNames).forEach(([folderName, iconId]) => {
const iconDef = theme.iconDefinitions[iconId];
if (iconDef) {
css += `.folder-icon[data-foldername="${folderName}"] { background-image: url(${iconDef.iconPath}); }\n`;
}
});
return css;
}
getIconThemes(): IconThemeInfo[] {
return Array.from(this.iconThemes.values());
}
getCurrentIconTheme(): IconThemeInfo | undefined {
return this.currentIconTheme ? this.iconThemes.get(this.currentIconTheme) : undefined;
}
onIconThemeChange(listener: (theme: IconThemeInfo) => void): TraeAPI.Disposable {
this.iconThemeChangeListeners.push(listener);
return {
dispose: () => {
const index = this.iconThemeChangeListeners.indexOf(listener);
if (index >= 0) {
this.iconThemeChangeListeners.splice(index, 1);
}
}
};
}
private notifyIconThemeChange(theme: IconThemeInfo): void {
this.iconThemeChangeListeners.forEach(listener => {
try {
listener(theme);
} catch (error) {
console.error('Error in icon theme change listener:', error);
}
});
}
private isValidIconTheme(themeData: any): boolean {
return (
themeData &&
typeof themeData === 'object' &&
themeData.id &&
themeData.label &&
themeData.iconDefinitions
);
}
dispose(): void {
this.iconThemes.clear();
this.iconThemeChangeListeners.length = 0;
this.currentIconTheme = null;
}
}
interface IconThemeInfo {
id: string;
label: string;
description: string;
isBuiltIn: boolean;
iconDefinitions: Record<string, { iconPath: string }>;
fileExtensions: Record<string, string>;
fileNames: Record<string, string>;
folderNames: Record<string, string>;
}
// Initialize icon theme manager
const iconThemeManager = new IconThemeManager();API Reference
Core Interfaces
typescript
interface ThemeAPI {
// Theme management
getThemes(): ThemeInfo[];
getCurrentTheme(): ThemeInfo | undefined;
applyTheme(themeId: string): Promise<boolean>;
// Custom themes
createCustomTheme(options: ThemeCreationOptions): Promise<string>;
updateCustomTheme(themeId: string, updates: Partial<ThemeCreationOptions>): Promise<boolean>;
deleteCustomTheme(themeId: string): Promise<boolean>;
// Import/Export
exportTheme(themeId: string): Promise<string | undefined>;
importTheme(filePath?: string): Promise<string | undefined>;
// Events
onThemeChange(listener: (theme: ThemeInfo) => void): Disposable;
// Icon themes
getIconThemes(): IconThemeInfo[];
getCurrentIconTheme(): IconThemeInfo | undefined;
applyIconTheme(themeId: string): Promise<boolean>;
onIconThemeChange(listener: (theme: IconThemeInfo) => void): Disposable;
}Best Practices
- Theme Structure: Follow standard theme file structure and naming conventions
- Color Accessibility: Ensure sufficient contrast ratios for accessibility
- Performance: Optimize theme loading and application for better performance
- Consistency: Maintain consistent color schemes across all UI elements
- User Experience: Provide smooth theme transitions and preview capabilities
- Documentation: Document custom theme properties and usage
- Testing: Test themes across different file types and UI states
- Compatibility: Ensure themes work across different platforms and configurations
Related APIs
- UI API - For UI theming and styling
- Editor API - For editor-specific theming
- Workspace API - For workspace-specific theme settings
- Extensions API - For theme extension development