主题 API
概述
Trae 主题 API 提供了强大的主题系统,允许开发者创建、管理和应用自定义主题。通过主题 API,可以定义颜色方案、图标主题、语法高亮等视觉元素。
核心概念
主题类型
typescript
enum ColorThemeKind {
Light = 1,
Dark = 2,
HighContrast = 3,
HighContrastLight = 4
}
interface ColorTheme {
readonly kind: ColorThemeKind;
}
interface IconTheme {
readonly id: string;
}
interface ProductIconTheme {
readonly id: string;
}颜色定义
typescript
class ThemeColor {
readonly id: string;
constructor(id: string);
static readonly foreground: ThemeColor;
static readonly errorForeground: ThemeColor;
static readonly descriptionForeground: ThemeColor;
static readonly iconForeground: ThemeColor;
static readonly focusBorder: ThemeColor;
static readonly contrastBorder: ThemeColor;
static readonly contrastActiveBorder: ThemeColor;
static readonly selection: ThemeColor;
static readonly textSeparatorForeground: ThemeColor;
static readonly textLinkForeground: ThemeColor;
static readonly textLinkActiveForeground: ThemeColor;
static readonly textPreformatForeground: ThemeColor;
static readonly textBlockQuoteBackground: ThemeColor;
static readonly textBlockQuoteBorder: ThemeColor;
static readonly textCodeBlockBackground: ThemeColor;
}
class ThemeIcon {
readonly id: string;
readonly color?: ThemeColor;
constructor(id: string, color?: ThemeColor);
static readonly file: ThemeIcon;
static readonly folder: ThemeIcon;
static readonly folderOpened: ThemeIcon;
static readonly rootFolder: ThemeIcon;
static readonly rootFolderOpened: ThemeIcon;
static readonly folderExpanded: ThemeIcon;
static readonly folderCollapsed: ThemeIcon;
}API 参考
主题管理
基础主题操作
typescript
import { window, workspace, ColorThemeKind, ThemeColor, ThemeIcon } from '@trae/api';
// 获取当前颜色主题
const getCurrentColorTheme = (): ColorTheme => {
return window.activeColorTheme;
};
// 监听主题变化
const onDidChangeActiveColorTheme = (
listener: (theme: ColorTheme) => void
): Disposable => {
return window.onDidChangeActiveColorTheme(listener);
};
// 获取当前图标主题
const getCurrentIconTheme = (): IconTheme | undefined => {
return window.activeIconTheme;
};
// 监听图标主题变化
const onDidChangeActiveIconTheme = (
listener: (theme: IconTheme | undefined) => void
): Disposable => {
return window.onDidChangeActiveIconTheme(listener);
};
// 检查主题类型
const isLightTheme = (theme: ColorTheme): boolean => {
return theme.kind === ColorThemeKind.Light ||
theme.kind === ColorThemeKind.HighContrastLight;
};
const isDarkTheme = (theme: ColorTheme): boolean => {
return theme.kind === ColorThemeKind.Dark;
};
const isHighContrastTheme = (theme: ColorTheme): boolean => {
return theme.kind === ColorThemeKind.HighContrast ||
theme.kind === ColorThemeKind.HighContrastLight;
};
// 主题适配函数
const getThemeAdaptiveColor = (lightColor: string, darkColor: string): string => {
const theme = window.activeColorTheme;
return isLightTheme(theme) ? lightColor : darkColor;
};
const getThemeAdaptiveIcon = (lightIcon: string, darkIcon: string): string => {
const theme = window.activeColorTheme;
return isLightTheme(theme) ? lightIcon : darkIcon;
};主题监听器
typescript
class ThemeManager {
private disposables: Disposable[] = [];
private themeChangeListeners: Set<(theme: ColorTheme) => void> = new Set();
constructor() {
this.setupThemeListeners();
}
private setupThemeListeners(): void {
this.disposables.push(
window.onDidChangeActiveColorTheme(theme => {
this.onThemeChanged(theme);
}),
window.onDidChangeActiveIconTheme(theme => {
this.onIconThemeChanged(theme);
})
);
}
private onThemeChanged(theme: ColorTheme): void {
console.log(`Theme changed to: ${ColorThemeKind[theme.kind]}`);
// 通知所有监听器
this.themeChangeListeners.forEach(listener => {
try {
listener(theme);
} catch (error) {
console.error('Error in theme change listener:', error);
}
});
// 更新主题相关的 UI 元素
this.updateThemeBasedUI(theme);
}
private onIconThemeChanged(theme: IconTheme | undefined): void {
console.log(`Icon theme changed to: ${theme?.id || 'none'}`);
this.updateIconThemeBasedUI(theme);
}
private updateThemeBasedUI(theme: ColorTheme): void {
// 更新状态栏项目颜色
this.updateStatusBarColors(theme);
// 更新 Webview 主题
this.updateWebviewThemes(theme);
// 更新装饰器颜色
this.updateDecorationColors(theme);
}
private updateIconThemeBasedUI(theme: IconTheme | undefined): void {
// 更新自定义图标
this.updateCustomIcons(theme);
}
private updateStatusBarColors(theme: ColorTheme): void {
// 根据主题更新状态栏项目的颜色
const isDark = isDarkTheme(theme);
const color = isDark ? '#ffffff' : '#000000';
// 更新状态栏项目
// 这里需要具体的状态栏项目引用
}
private updateWebviewThemes(theme: ColorTheme): void {
// 向 Webview 发送主题更新消息
const themeData = {
kind: theme.kind,
isDark: isDarkTheme(theme),
isLight: isLightTheme(theme),
isHighContrast: isHighContrastTheme(theme)
};
// 发送到所有 Webview
// webviewPanel.webview.postMessage({ type: 'themeChanged', theme: themeData });
}
private updateDecorationColors(theme: ColorTheme): void {
// 更新文本装饰器的颜色
const decorationType = window.createTextEditorDecorationType({
backgroundColor: isLightTheme(theme) ? '#ffeb3b33' : '#ffeb3b22',
border: `1px solid ${isLightTheme(theme) ? '#ffeb3b' : '#ffeb3b88'}`
});
// 应用装饰器
// editor.setDecorations(decorationType, ranges);
}
private updateCustomIcons(theme: IconTheme | undefined): void {
// 根据图标主题更新自定义图标
if (theme) {
console.log(`Updating icons for theme: ${theme.id}`);
}
}
// 公共方法
addThemeChangeListener(listener: (theme: ColorTheme) => void): Disposable {
this.themeChangeListeners.add(listener);
return {
dispose: () => {
this.themeChangeListeners.delete(listener);
}
};
}
getCurrentThemeInfo(): ThemeInfo {
const colorTheme = window.activeColorTheme;
const iconTheme = window.activeIconTheme;
return {
colorTheme: {
kind: colorTheme.kind,
kindName: ColorThemeKind[colorTheme.kind],
isDark: isDarkTheme(colorTheme),
isLight: isLightTheme(colorTheme),
isHighContrast: isHighContrastTheme(colorTheme)
},
iconTheme: iconTheme ? {
id: iconTheme.id
} : undefined
};
}
dispose(): void {
this.disposables.forEach(d => d.dispose());
this.themeChangeListeners.clear();
}
}
interface ThemeInfo {
colorTheme: {
kind: ColorThemeKind;
kindName: string;
isDark: boolean;
isLight: boolean;
isHighContrast: boolean;
};
iconTheme?: {
id: string;
};
}颜色主题定义
主题贡献点
typescript
// package.json 中的主题定义
const themeContribution = {
"contributes": {
"themes": [
{
"label": "My Dark Theme",
"uiTheme": "vs-dark",
"path": "./themes/dark-theme.json"
},
{
"label": "My Light Theme",
"uiTheme": "vs",
"path": "./themes/light-theme.json"
},
{
"label": "My High Contrast Theme",
"uiTheme": "hc-black",
"path": "./themes/high-contrast-theme.json"
}
],
"iconThemes": [
{
"id": "my-icon-theme",
"label": "My Icon Theme",
"path": "./themes/icon-theme.json"
}
],
"productIconThemes": [
{
"id": "my-product-icon-theme",
"label": "My Product Icon Theme",
"path": "./themes/product-icon-theme.json"
}
]
}
};颜色主题文件结构
typescript
// dark-theme.json
const darkTheme = {
"name": "My Dark Theme",
"type": "dark",
"colors": {
// 编辑器颜色
"editor.background": "#1e1e1e",
"editor.foreground": "#d4d4d4",
"editor.selectionBackground": "#264f78",
"editor.lineHighlightBackground": "#2a2d2e",
"editor.cursor": "#ffffff",
// 侧边栏颜色
"sideBar.background": "#252526",
"sideBar.foreground": "#cccccc",
"sideBar.border": "#2b2b2b",
// 活动栏颜色
"activityBar.background": "#2c2c2c",
"activityBar.foreground": "#ffffff",
"activityBar.border": "#2b2b2b",
// 状态栏颜色
"statusBar.background": "#007acc",
"statusBar.foreground": "#ffffff",
"statusBar.noFolderBackground": "#68217a",
// 标题栏颜色
"titleBar.activeBackground": "#3c3c3c",
"titleBar.activeForeground": "#cccccc",
"titleBar.inactiveBackground": "#3c3c3c",
"titleBar.inactiveForeground": "#cccccc99",
// 面板颜色
"panel.background": "#1e1e1e",
"panel.border": "#2b2b2b",
"panelTitle.activeForeground": "#ffffff",
"panelTitle.inactiveForeground": "#cccccc99",
// 终端颜色
"terminal.background": "#1e1e1e",
"terminal.foreground": "#d4d4d4",
"terminal.ansiBlack": "#000000",
"terminal.ansiRed": "#cd3131",
"terminal.ansiGreen": "#0dbc79",
"terminal.ansiYellow": "#e5e510",
"terminal.ansiBlue": "#2472c8",
"terminal.ansiMagenta": "#bc3fbc",
"terminal.ansiCyan": "#11a8cd",
"terminal.ansiWhite": "#e5e5e5",
// 按钮颜色
"button.background": "#0e639c",
"button.foreground": "#ffffff",
"button.hoverBackground": "#1177bb",
// 输入框颜色
"input.background": "#3c3c3c",
"input.foreground": "#cccccc",
"input.border": "#3c3c3c",
"input.placeholderForeground": "#cccccc99",
// 下拉框颜色
"dropdown.background": "#3c3c3c",
"dropdown.foreground": "#cccccc",
"dropdown.border": "#3c3c3c",
// 列表颜色
"list.activeSelectionBackground": "#094771",
"list.activeSelectionForeground": "#ffffff",
"list.inactiveSelectionBackground": "#37373d",
"list.inactiveSelectionForeground": "#cccccc",
"list.hoverBackground": "#2a2d2e",
"list.hoverForeground": "#cccccc",
// 树视图颜色
"tree.indentGuidesStroke": "#585858",
// 标签页颜色
"tab.activeBackground": "#1e1e1e",
"tab.activeForeground": "#ffffff",
"tab.inactiveBackground": "#2d2d30",
"tab.inactiveForeground": "#cccccc99",
"tab.border": "#2b2b2b",
// 编辑器组颜色
"editorGroup.border": "#2b2b2b",
"editorGroupHeader.tabsBackground": "#252526",
// 滚动条颜色
"scrollbar.shadow": "#000000",
"scrollbarSlider.background": "#79797966",
"scrollbarSlider.hoverBackground": "#646464b3",
"scrollbarSlider.activeBackground": "#bfbfbf66",
// 徽章颜色
"badge.background": "#007acc",
"badge.foreground": "#ffffff",
// 进度条颜色
"progressBar.background": "#0e70c0",
// 编辑器小部件颜色
"editorWidget.background": "#252526",
"editorWidget.border": "#454545",
"editorWidget.resizeBorder": "#007acc",
// 查找小部件颜色
"editorFind.currentMatchHighlight": "#515c6a",
"editorFind.otherMatchesHighlight": "#515c6a",
"editorFind.rangeHighlight": "#3a3d41",
// 悬停小部件颜色
"editorHoverWidget.background": "#252526",
"editorHoverWidget.border": "#454545",
// 建议小部件颜色
"editorSuggestWidget.background": "#252526",
"editorSuggestWidget.border": "#454545",
"editorSuggestWidget.selectedBackground": "#094771",
// 错误和警告颜色
"editorError.foreground": "#f14c4c",
"editorWarning.foreground": "#ffb454",
"editorInfo.foreground": "#3794ff",
"editorHint.foreground": "#eeeeeeb3"
},
"tokenColors": [
{
"name": "Comment",
"scope": [
"comment",
"punctuation.definition.comment"
],
"settings": {
"fontStyle": "italic",
"foreground": "#6A9955"
}
},
{
"name": "Variables",
"scope": [
"variable",
"string constant.other.placeholder"
],
"settings": {
"foreground": "#9CDCFE"
}
},
{
"name": "Colors",
"scope": [
"constant.other.color"
],
"settings": {
"foreground": "#ffffff"
}
},
{
"name": "Invalid",
"scope": [
"invalid",
"invalid.illegal"
],
"settings": {
"foreground": "#f44747"
}
},
{
"name": "Keyword, Storage",
"scope": [
"keyword",
"storage.type",
"storage.modifier"
],
"settings": {
"foreground": "#569CD6"
}
},
{
"name": "Operator, Misc",
"scope": [
"keyword.control",
"constant.other.color",
"punctuation",
"meta.tag",
"punctuation.definition.tag",
"punctuation.separator.inheritance.php",
"punctuation.definition.tag.html",
"punctuation.definition.tag.begin.html",
"punctuation.definition.tag.end.html",
"punctuation.section.embedded",
"keyword.other.template",
"keyword.other.substitution"
],
"settings": {
"foreground": "#D4D4D4"
}
},
{
"name": "Tag",
"scope": [
"entity.name.tag",
"meta.tag.sgml",
"markup.deleted.git_gutter"
],
"settings": {
"foreground": "#569CD6"
}
},
{
"name": "Function, Special Method",
"scope": [
"entity.name.function",
"meta.function-call",
"variable.function",
"support.function",
"keyword.other.special-method"
],
"settings": {
"foreground": "#DCDCAA"
}
},
{
"name": "Block Level Variables",
"scope": [
"meta.block variable.other"
],
"settings": {
"foreground": "#9CDCFE"
}
},
{
"name": "Other Variable, String Link",
"scope": [
"support.other.variable",
"string.other.link"
],
"settings": {
"foreground": "#9CDCFE"
}
},
{
"name": "Number, Constant, Function Argument, Tag Attribute, Embedded",
"scope": [
"constant.numeric",
"constant.language",
"support.constant",
"constant.character",
"constant.escape",
"variable.parameter",
"keyword.other.unit",
"keyword.other"
],
"settings": {
"foreground": "#B5CEA8"
}
},
{
"name": "String, Symbols, Inherited Class, Markup Heading",
"scope": [
"string",
"constant.other.symbol",
"constant.other.key",
"entity.other.inherited-class",
"markup.heading",
"markup.inserted.git_gutter",
"meta.group.braces.curly constant.other.object.key.js string.unquoted.label.js"
],
"settings": {
"foreground": "#CE9178"
}
},
{
"name": "Class, Support",
"scope": [
"entity.name",
"support.type",
"support.class",
"support.other.namespace.use.php",
"meta.use.php",
"support.other.namespace.php",
"markup.changed.git_gutter",
"support.type.sys-types"
],
"settings": {
"foreground": "#4EC9B0"
}
},
{
"name": "Entity Types",
"scope": [
"support.type"
],
"settings": {
"foreground": "#4EC9B0"
}
},
{
"name": "CSS Class and Support",
"scope": [
"source.css support.type.property-name",
"source.sass support.type.property-name",
"source.scss support.type.property-name",
"source.less support.type.property-name",
"source.stylus support.type.property-name",
"source.postcss support.type.property-name"
],
"settings": {
"foreground": "#9CDCFE"
}
},
{
"name": "Sub-methods",
"scope": [
"entity.name.module.js",
"variable.import.parameter.js",
"variable.other.class.js"
],
"settings": {
"foreground": "#FF8C00"
}
},
{
"name": "Language methods",
"scope": [
"variable.language"
],
"settings": {
"fontStyle": "italic",
"foreground": "#569CD6"
}
},
{
"name": "entity.name.method.js",
"scope": [
"entity.name.method.js"
],
"settings": {
"fontStyle": "italic",
"foreground": "#DCDCAA"
}
},
{
"name": "meta.method.js",
"scope": [
"meta.class-method.js entity.name.function.js",
"variable.function.constructor"
],
"settings": {
"foreground": "#DCDCAA"
}
},
{
"name": "Attributes",
"scope": [
"entity.other.attribute-name"
],
"settings": {
"foreground": "#9CDCFE"
}
},
{
"name": "HTML Attributes",
"scope": [
"text.html.basic entity.other.attribute-name.html",
"text.html.basic entity.other.attribute-name"
],
"settings": {
"fontStyle": "italic",
"foreground": "#9CDCFE"
}
},
{
"name": "CSS Classes",
"scope": [
"entity.other.attribute-name.class"
],
"settings": {
"foreground": "#FFCB6B"
}
},
{
"name": "CSS ID's",
"scope": [
"source.sass keyword.control"
],
"settings": {
"foreground": "#569CD6"
}
},
{
"name": "Inserted",
"scope": [
"markup.inserted"
],
"settings": {
"foreground": "#B5CEA8"
}
},
{
"name": "Deleted",
"scope": [
"markup.deleted"
],
"settings": {
"foreground": "#CE9178"
}
},
{
"name": "Changed",
"scope": [
"markup.changed"
],
"settings": {
"foreground": "#569CD6"
}
},
{
"name": "Regular Expressions",
"scope": [
"string.regexp"
],
"settings": {
"foreground": "#D16969"
}
},
{
"name": "Escape Characters",
"scope": [
"constant.character.escape"
],
"settings": {
"foreground": "#D7BA7D"
}
},
{
"name": "URL",
"scope": [
"*url*",
"*link*",
"*uri*"
],
"settings": {
"fontStyle": "underline"
}
},
{
"name": "Decorators",
"scope": [
"tag.decorator.js entity.name.tag.js",
"tag.decorator.js punctuation.definition.tag.js"
],
"settings": {
"fontStyle": "italic",
"foreground": "#569CD6"
}
},
{
"name": "ES7 Bind Operator",
"scope": [
"source.js constant.other.object.key.js string.unquoted.label.js"
],
"settings": {
"fontStyle": "italic",
"foreground": "#FF8C00"
}
},
{
"name": "JSON Key - Level 0",
"scope": [
"source.json meta.structure.dictionary.json support.type.property-name.json"
],
"settings": {
"foreground": "#9CDCFE"
}
},
{
"name": "JSON Key - Level 1",
"scope": [
"source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json"
],
"settings": {
"foreground": "#FFCB6B"
}
},
{
"name": "JSON Key - Level 2",
"scope": [
"source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json"
],
"settings": {
"foreground": "#F78C6C"
}
},
{
"name": "JSON Key - Level 3",
"scope": [
"source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json"
],
"settings": {
"foreground": "#FF5370"
}
},
{
"name": "Markdown - Plain",
"scope": [
"text.html.markdown",
"punctuation.definition.list_item.markdown"
],
"settings": {
"foreground": "#CCCCCC"
}
},
{
"name": "Markdown - Markup Raw Inline",
"scope": [
"text.html.markdown markup.inline.raw.markdown"
],
"settings": {
"foreground": "#569CD6"
}
},
{
"name": "Markdown - Markup Raw Inline Punctuation",
"scope": [
"text.html.markdown markup.inline.raw.markdown punctuation.definition.raw.markdown"
],
"settings": {
"foreground": "#65737E"
}
},
{
"name": "Markdown - Heading",
"scope": [
"markdown.heading",
"markup.heading | markup.heading entity.name",
"markup.heading.markdown punctuation.definition.heading.markdown"
],
"settings": {
"foreground": "#569CD6"
}
},
{
"name": "Markup - Italic",
"scope": [
"markup.italic"
],
"settings": {
"fontStyle": "italic",
"foreground": "#F78C6C"
}
},
{
"name": "Markup - Bold",
"scope": [
"markup.bold",
"markup.bold string"
],
"settings": {
"fontStyle": "bold",
"foreground": "#F78C6C"
}
},
{
"name": "Markup - Bold-Italic",
"scope": [
"markup.bold markup.italic",
"markup.italic markup.bold",
"markup.quote markup.bold",
"markup.bold markup.italic string",
"markup.italic markup.bold string",
"markup.quote markup.bold string"
],
"settings": {
"fontStyle": "bold italic",
"foreground": "#F78C6C"
}
},
{
"name": "Markup - Underline",
"scope": [
"markup.underline"
],
"settings": {
"fontStyle": "underline",
"foreground": "#F78C6C"
}
},
{
"name": "Markdown - Blockquote",
"scope": [
"markup.quote punctuation.definition.blockquote.markdown"
],
"settings": {
"foreground": "#65737E"
}
},
{
"name": "Markup - Quote",
"scope": [
"markup.quote"
],
"settings": {
"fontStyle": "italic"
}
},
{
"name": "Markdown - Link",
"scope": [
"string.other.link.title.markdown"
],
"settings": {
"foreground": "#DCDCAA"
}
},
{
"name": "Markdown - Link Description",
"scope": [
"string.other.link.description.title.markdown"
],
"settings": {
"foreground": "#569CD6"
}
},
{
"name": "Markdown - Link Anchor",
"scope": [
"constant.other.reference.link.markdown"
],
"settings": {
"foreground": "#FFCB6B"
}
},
{
"name": "Markup - Raw Block",
"scope": [
"markup.raw.block"
],
"settings": {
"foreground": "#569CD6"
}
},
{
"name": "Markdown - Raw Block Fenced",
"scope": [
"markup.raw.block.fenced.markdown"
],
"settings": {
"foreground": "#00000050"
}
},
{
"name": "Markdown - Fenced Bode Block",
"scope": [
"punctuation.definition.fenced.markdown"
],
"settings": {
"foreground": "#00000050"
}
},
{
"name": "Markdown - Fenced Bode Block Variable",
"scope": [
"markup.raw.block.fenced.markdown",
"variable.language.fenced.markdown",
"punctuation.section.class.end"
],
"settings": {
"foreground": "#CCCCCC"
}
},
{
"name": "Markdown - Fenced Language",
"scope": [
"variable.language.fenced.markdown"
],
"settings": {
"foreground": "#65737E"
}
},
{
"name": "Markdown - Separator",
"scope": [
"meta.separator"
],
"settings": {
"fontStyle": "bold",
"foreground": "#65737E"
}
},
{
"name": "Markup - Table",
"scope": [
"markup.table"
],
"settings": {
"foreground": "#CCCCCC"
}
}
]
};图标主题文件结构
typescript
// icon-theme.json
const iconTheme = {
"iconDefinitions": {
"_file": {
"iconPath": "./icons/file.svg"
},
"_folder": {
"iconPath": "./icons/folder.svg"
},
"_folderOpen": {
"iconPath": "./icons/folder-open.svg"
},
"_js": {
"iconPath": "./icons/javascript.svg"
},
"_ts": {
"iconPath": "./icons/typescript.svg"
},
"_json": {
"iconPath": "./icons/json.svg"
},
"_html": {
"iconPath": "./icons/html.svg"
},
"_css": {
"iconPath": "./icons/css.svg"
},
"_md": {
"iconPath": "./icons/markdown.svg"
},
"_git": {
"iconPath": "./icons/git.svg"
},
"_npm": {
"iconPath": "./icons/npm.svg"
},
"_node_modules": {
"iconPath": "./icons/node_modules.svg"
}
},
"file": "_file",
"folder": "_folder",
"folderExpanded": "_folderOpen",
"fileExtensions": {
"js": "_js",
"ts": "_ts",
"json": "_json",
"html": "_html",
"htm": "_html",
"css": "_css",
"scss": "_css",
"sass": "_css",
"less": "_css",
"md": "_md",
"markdown": "_md"
},
"fileNames": {
"package.json": "_npm",
"package-lock.json": "_npm",
"yarn.lock": "_npm",
".gitignore": "_git",
".gitattributes": "_git",
"README.md": "_md",
"CHANGELOG.md": "_md",
"LICENSE": "_file"
},
"folderNames": {
"node_modules": "_node_modules",
".git": "_git",
"src": "_folder",
"dist": "_folder",
"build": "_folder",
"public": "_folder",
"assets": "_folder",
"images": "_folder",
"styles": "_folder",
"components": "_folder",
"pages": "_folder",
"utils": "_folder",
"lib": "_folder",
"libs": "_folder",
"test": "_folder",
"tests": "_folder",
"spec": "_folder",
"docs": "_folder",
"documentation": "_folder"
},
"folderNamesExpanded": {
"node_modules": "_node_modules",
".git": "_git",
"src": "_folderOpen",
"dist": "_folderOpen",
"build": "_folderOpen",
"public": "_folderOpen",
"assets": "_folderOpen",
"images": "_folderOpen",
"styles": "_folderOpen",
"components": "_folderOpen",
"pages": "_folderOpen",
"utils": "_folderOpen",
"lib": "_folderOpen",
"libs": "_folderOpen",
"test": "_folderOpen",
"tests": "_folderOpen",
"spec": "_folderOpen",
"docs": "_folderOpen",
"documentation": "_folderOpen"
},
"languageIds": {
"javascript": "_js",
"typescript": "_ts",
"json": "_json",
"jsonc": "_json",
"html": "_html",
"css": "_css",
"scss": "_css",
"sass": "_css",
"less": "_css",
"markdown": "_md"
},
"light": {
"fileExtensions": {
"js": "_js_light",
"ts": "_ts_light"
},
"file": "_file_light",
"folder": "_folder_light"
},
"highContrast": {
"fileExtensions": {
"js": "_js_hc",
"ts": "_ts_hc"
},
"file": "_file_hc",
"folder": "_folder_hc"
}
};主题工具类
主题颜色工具
typescript
class ThemeColorUtils {
// 颜色转换工具
static hexToRgb(hex: string): { r: number; g: number; b: number } | null {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
static rgbToHex(r: number, g: number, b: number): string {
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
}
static hexToHsl(hex: string): { h: number; s: number; l: number } | null {
const rgb = this.hexToRgb(hex);
if (!rgb) return null;
const { r, g, b } = rgb;
const rNorm = r / 255;
const gNorm = g / 255;
const bNorm = b / 255;
const max = Math.max(rNorm, gNorm, bNorm);
const min = Math.min(rNorm, gNorm, bNorm);
const diff = max - min;
let h = 0;
let s = 0;
const l = (max + min) / 2;
if (diff !== 0) {
s = l > 0.5 ? diff / (2 - max - min) : diff / (max + min);
switch (max) {
case rNorm:
h = (gNorm - bNorm) / diff + (gNorm < bNorm ? 6 : 0);
break;
case gNorm:
h = (bNorm - rNorm) / diff + 2;
break;
case bNorm:
h = (rNorm - gNorm) / diff + 4;
break;
}
h /= 6;
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
l: Math.round(l * 100)
};
}
// 颜色操作工具
static lighten(hex: string, amount: number): string {
const hsl = this.hexToHsl(hex);
if (!hsl) return hex;
hsl.l = Math.min(100, hsl.l + amount);
return this.hslToHex(hsl.h, hsl.s, hsl.l);
}
static darken(hex: string, amount: number): string {
const hsl = this.hexToHsl(hex);
if (!hsl) return hex;
hsl.l = Math.max(0, hsl.l - amount);
return this.hslToHex(hsl.h, hsl.s, hsl.l);
}
static saturate(hex: string, amount: number): string {
const hsl = this.hexToHsl(hex);
if (!hsl) return hex;
hsl.s = Math.min(100, hsl.s + amount);
return this.hslToHex(hsl.h, hsl.s, hsl.l);
}
static desaturate(hex: string, amount: number): string {
const hsl = this.hexToHsl(hex);
if (!hsl) return hex;
hsl.s = Math.max(0, hsl.s - amount);
return this.hslToHex(hsl.h, hsl.s, hsl.l);
}
static adjustHue(hex: string, amount: number): string {
const hsl = this.hexToHsl(hex);
if (!hsl) return hex;
hsl.h = (hsl.h + amount) % 360;
if (hsl.h < 0) hsl.h += 360;
return this.hslToHex(hsl.h, hsl.s, hsl.l);
}
static mix(color1: string, color2: string, weight = 50): string {
const rgb1 = this.hexToRgb(color1);
const rgb2 = this.hexToRgb(color2);
if (!rgb1 || !rgb2) return color1;
const w = weight / 100;
const r = Math.round(rgb1.r * (1 - w) + rgb2.r * w);
const g = Math.round(rgb1.g * (1 - w) + rgb2.g * w);
const b = Math.round(rgb1.b * (1 - w) + rgb2.b * w);
return this.rgbToHex(r, g, b);
}
static getContrastRatio(color1: string, color2: string): number {
const rgb1 = this.hexToRgb(color1);
const rgb2 = this.hexToRgb(color2);
if (!rgb1 || !rgb2) return 1;
const l1 = this.getLuminance(rgb1.r, rgb1.g, rgb1.b);
const l2 = this.getLuminance(rgb2.r, rgb2.g, rgb2.b);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
static isAccessible(foreground: string, background: string, level: 'AA' | 'AAA' = 'AA'): boolean {
const ratio = this.getContrastRatio(foreground, background);
return level === 'AA' ? ratio >= 4.5 : ratio >= 7;
}
private static hslToHex(h: number, s: number, l: number): string {
const hNorm = h / 360;
const sNorm = s / 100;
const lNorm = l / 100;
const c = (1 - Math.abs(2 * lNorm - 1)) * sNorm;
const x = c * (1 - Math.abs((hNorm * 6) % 2 - 1));
const m = lNorm - c / 2;
let r = 0, g = 0, b = 0;
if (0 <= hNorm && hNorm < 1/6) {
r = c; g = x; b = 0;
} else if (1/6 <= hNorm && hNorm < 2/6) {
r = x; g = c; b = 0;
} else if (2/6 <= hNorm && hNorm < 3/6) {
r = 0; g = c; b = x;
} else if (3/6 <= hNorm && hNorm < 4/6) {
r = 0; g = x; b = c;
} else if (4/6 <= hNorm && hNorm < 5/6) {
r = x; g = 0; b = c;
} else if (5/6 <= hNorm && hNorm < 1) {
r = c; g = 0; b = x;
}
r = Math.round((r + m) * 255);
g = Math.round((g + m) * 255);
b = Math.round((b + m) * 255);
return this.rgbToHex(r, g, b);
}
private static getLuminance(r: number, g: number, b: number): number {
const [rNorm, gNorm, bNorm] = [r, g, b].map(c => {
c = c / 255;
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
});
return 0.2126 * rNorm + 0.7152 * gNorm + 0.0722 * bNorm;
}
// 主题颜色生成器
static generateColorPalette(baseColor: string): ColorPalette {
return {
primary: baseColor,
primaryLight: this.lighten(baseColor, 20),
primaryDark: this.darken(baseColor, 20),
secondary: this.adjustHue(baseColor, 180),
secondaryLight: this.lighten(this.adjustHue(baseColor, 180), 20),
secondaryDark: this.darken(this.adjustHue(baseColor, 180), 20),
accent: this.adjustHue(baseColor, 60),
accentLight: this.lighten(this.adjustHue(baseColor, 60), 20),
accentDark: this.darken(this.adjustHue(baseColor, 60), 20),
neutral: this.desaturate(baseColor, 80),
neutralLight: this.lighten(this.desaturate(baseColor, 80), 40),
neutralDark: this.darken(this.desaturate(baseColor, 80), 40)
};
}
static generateThemeColors(palette: ColorPalette, isDark = false): ThemeColors {
const base = isDark ? {
background: '#1e1e1e',
foreground: '#d4d4d4',
surface: '#252526',
border: '#2b2b2b'
} : {
background: '#ffffff',
foreground: '#333333',
surface: '#f8f8f8',
border: '#e0e0e0'
};
return {
...base,
primary: palette.primary,
primaryHover: isDark ? palette.primaryLight : palette.primaryDark,
secondary: palette.secondary,
secondaryHover: isDark ? palette.secondaryLight : palette.secondaryDark,
accent: palette.accent,
accentHover: isDark ? palette.accentLight : palette.accentDark,
success: isDark ? '#4caf50' : '#2e7d32',
warning: isDark ? '#ff9800' : '#f57c00',
error: isDark ? '#f44336' : '#c62828',
info: isDark ? '#2196f3' : '#1565c0'
};
}
}
interface ColorPalette {
primary: string;
primaryLight: string;
primaryDark: string;
secondary: string;
secondaryLight: string;
secondaryDark: string;
accent: string;
accentLight: string;
accentDark: string;
neutral: string;
neutralLight: string;
neutralDark: string;
}
interface ThemeColors {
background: string;
foreground: string;
surface: string;
border: string;
primary: string;
primaryHover: string;
secondary: string;
secondaryHover: string;
accent: string;
accentHover: string;
success: string;
warning: string;
error: string;
info: string;
}动态主题切换
主题切换器
typescript
class ThemeSwitcher {
private currentTheme: ColorTheme;
private themePresets: Map<string, ThemePreset> = new Map();
private disposables: Disposable[] = [];
constructor() {
this.currentTheme = window.activeColorTheme;
this.setupThemePresets();
this.setupEventListeners();
}
private setupThemePresets(): void {
this.themePresets.set('auto', {
name: 'Auto (System)',
description: 'Follow system theme',
apply: () => this.applySystemTheme()
});
this.themePresets.set('light', {
name: 'Light Theme',
description: 'Light color scheme',
apply: () => this.applyTheme('Default Light+')
});
this.themePresets.set('dark', {
name: 'Dark Theme',
description: 'Dark color scheme',
apply: () => this.applyTheme('Default Dark+')
});
this.themePresets.set('highContrast', {
name: 'High Contrast',
description: 'High contrast theme',
apply: () => this.applyTheme('Default High Contrast')
});
}
private setupEventListeners(): void {
this.disposables.push(
window.onDidChangeActiveColorTheme(theme => {
this.currentTheme = theme;
this.onThemeChanged(theme);
})
);
}
private onThemeChanged(theme: ColorTheme): void {
// 保存主题偏好
this.saveThemePreference(theme);
// 通知主题变化
this.notifyThemeChange(theme);
}
private async applyTheme(themeId: string): Promise<void> {
try {
await commands.executeCommand('workbench.action.selectTheme', themeId);
} catch (error) {
console.error(`Failed to apply theme: ${themeId}`, error);
window.showErrorMessage(`Failed to apply theme: ${themeId}`);
}
}
private async applySystemTheme(): Promise<void> {
// 检测系统主题
const isSystemDark = await this.detectSystemTheme();
const themeId = isSystemDark ? 'Default Dark+' : 'Default Light+';
await this.applyTheme(themeId);
}
private async detectSystemTheme(): Promise<boolean> {
// 这里需要实现系统主题检测逻辑
// 在实际实现中,可能需要使用平台特定的 API
return window.activeColorTheme.kind === ColorThemeKind.Dark;
}
private saveThemePreference(theme: ColorTheme): void {
const config = workspace.getConfiguration('workbench');
const themeKind = ColorThemeKind[theme.kind].toLowerCase();
// 保存到用户设置
config.update('preferredTheme', themeKind, ConfigurationTarget.Global);
}
private notifyThemeChange(theme: ColorTheme): void {
const themeInfo = {
kind: theme.kind,
kindName: ColorThemeKind[theme.kind],
isDark: isDarkTheme(theme),
isLight: isLightTheme(theme),
isHighContrast: isHighContrastTheme(theme)
};
// 发送主题变化事件
this.emitThemeChangeEvent(themeInfo);
}
private emitThemeChangeEvent(themeInfo: any): void {
// 这里可以实现自定义事件发射逻辑
console.log('Theme changed:', themeInfo);
}
// 公共方法
async switchToTheme(presetName: string): Promise<void> {
const preset = this.themePresets.get(presetName);
if (!preset) {
throw new Error(`Theme preset not found: ${presetName}`);
}
await preset.apply();
}
getAvailableThemes(): ThemePreset[] {
return Array.from(this.themePresets.values());
}
getCurrentTheme(): ColorTheme {
return this.currentTheme;
}
isCurrentTheme(kind: ColorThemeKind): boolean {
return this.currentTheme.kind === kind;
}
async toggleTheme(): Promise<void> {
const isDark = isDarkTheme(this.currentTheme);
const targetPreset = isDark ? 'light' : 'dark';
await this.switchToTheme(targetPreset);
}
dispose(): void {
this.disposables.forEach(d => d.dispose());
this.themePresets.clear();
}
}
interface ThemePreset {
name: string;
description: string;
apply: () => Promise<void>;
}Webview 主题集成
Webview 主题支持
typescript
class WebviewThemeProvider {
private webviewPanels: Set<WebviewPanel> = new Set();
private disposables: Disposable[] = [];
constructor() {
this.setupThemeListener();
}
private setupThemeListener(): void {
this.disposables.push(
window.onDidChangeActiveColorTheme(theme => {
this.updateWebviewThemes(theme);
})
);
}
registerWebviewPanel(panel: WebviewPanel): void {
this.webviewPanels.add(panel);
// 发送当前主题
this.sendThemeToWebview(panel, window.activeColorTheme);
// 清理
panel.onDidDispose(() => {
this.webviewPanels.delete(panel);
});
}
private updateWebviewThemes(theme: ColorTheme): void {
this.webviewPanels.forEach(panel => {
this.sendThemeToWebview(panel, theme);
});
}
private sendThemeToWebview(panel: WebviewPanel, theme: ColorTheme): void {
const themeData = this.getWebviewThemeData(theme);
panel.webview.postMessage({
type: 'themeChanged',
theme: themeData
});
}
private getWebviewThemeData(theme: ColorTheme): WebviewThemeData {
const isDark = isDarkTheme(theme);
const isLight = isLightTheme(theme);
const isHighContrast = isHighContrastTheme(theme);
return {
kind: theme.kind,
kindName: ColorThemeKind[theme.kind],
isDark,
isLight,
isHighContrast,
colors: this.getThemeColors(theme),
cssVariables: this.generateCSSVariables(theme)
};
}
private getThemeColors(theme: ColorTheme): Record<string, string> {
// 这里需要从当前主题中提取颜色
// 实际实现中可能需要访问主题文件或使用 API
const isDark = isDarkTheme(theme);
return {
background: isDark ? '#1e1e1e' : '#ffffff',
foreground: isDark ? '#d4d4d4' : '#333333',
primary: isDark ? '#007acc' : '#0066cc',
secondary: isDark ? '#68217a' : '#5a1a6b',
success: isDark ? '#4caf50' : '#2e7d32',
warning: isDark ? '#ff9800' : '#f57c00',
error: isDark ? '#f44336' : '#c62828',
info: isDark ? '#2196f3' : '#1565c0',
border: isDark ? '#2b2b2b' : '#e0e0e0',
surface: isDark ? '#252526' : '#f8f8f8'
};
}
private generateCSSVariables(theme: ColorTheme): string {
const colors = this.getThemeColors(theme);
return Object.entries(colors)
.map(([key, value]) => `--trae-${key}: ${value};`)
.join('\n');
}
getWebviewHTML(content: string): string {
const theme = window.activeColorTheme;
const themeData = this.getWebviewThemeData(theme);
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trae Webview</title>
<style>
:root {
${themeData.cssVariables}
}
body {
background-color: var(--trae-background);
color: var(--trae-foreground);
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
margin: 0;
padding: 16px;
}
.theme-${themeData.kindName.toLowerCase().replace(/\s+/g, '-')} {
/* 主题特定样式 */
}
.button {
background-color: var(--trae-primary);
color: var(--trae-background);
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-family: inherit;
}
.button:hover {
opacity: 0.8;
}
.card {
background-color: var(--trae-surface);
border: 1px solid var(--trae-border);
border-radius: 4px;
padding: 16px;
margin: 8px 0;
}
.text-success { color: var(--trae-success); }
.text-warning { color: var(--trae-warning); }
.text-error { color: var(--trae-error); }
.text-info { color: var(--trae-info); }
</style>
</head>
<body class="theme-${themeData.kindName.toLowerCase().replace(/\s+/g, '-')}">
${content}
<script>
// 主题变化监听
window.addEventListener('message', event => {
const message = event.data;
if (message.type === 'themeChanged') {
updateTheme(message.theme);
}
});
function updateTheme(themeData) {
// 更新 CSS 变量
const root = document.documentElement;
Object.entries(themeData.colors).forEach(([key, value]) => {
root.style.setProperty(\`--trae-\${key}\`, value);
});
// 更新 body 类名
document.body.className = \`theme-\${themeData.kindName.toLowerCase().replace(/\\s+/g, '-')}\`;
// 触发自定义事件
document.dispatchEvent(new CustomEvent('themeChanged', {
detail: themeData
}));
}
// 初始主题数据
const initialTheme = ${JSON.stringify(themeData)};
updateTheme(initialTheme);
</script>
</body>
</html>
`;
}
dispose(): void {
this.disposables.forEach(d => d.dispose());
this.webviewPanels.clear();
}
}
interface WebviewThemeData {
kind: ColorThemeKind;
kindName: string;
isDark: boolean;
isLight: boolean;
isHighContrast: boolean;
colors: Record<string, string>;
cssVariables: string;
}最佳实践
主题适配
typescript
// 1. 使用主题颜色而不是硬编码颜色
const decorationType = window.createTextEditorDecorationType({
backgroundColor: new ThemeColor('editor.selectionHighlightBackground'),
border: `1px solid ${new ThemeColor('editor.selectionHighlightBorder')}`
});
// 2. 响应主题变化
const themeListener = window.onDidChangeActiveColorTheme(theme => {
// 更新 UI 元素
updateStatusBarColors(theme);
updateDecorationColors(theme);
});
// 3. 提供主题特定的资源
const getIconPath = (iconName: string): string => {
const theme = window.activeColorTheme;
const themeFolder = isLightTheme(theme) ? 'light' : 'dark';
return path.join(extensionPath, 'icons', themeFolder, `${iconName}.svg`);
};
// 4. 使用条件样式
const getButtonStyle = (): string => {
const theme = window.activeColorTheme;
if (isHighContrastTheme(theme)) {
return 'high-contrast-button';
} else if (isDarkTheme(theme)) {
return 'dark-button';
} else {
return 'light-button';
}
};性能优化
typescript
// 1. 缓存主题相关计算
class ThemeCache {
private cache = new Map<string, any>();
private currentThemeKind: ColorThemeKind;
constructor() {
this.currentThemeKind = window.activeColorTheme.kind;
window.onDidChangeActiveColorTheme(theme => {
if (theme.kind !== this.currentThemeKind) {
this.cache.clear();
this.currentThemeKind = theme.kind;
}
});
}
get<T>(key: string, factory: () => T): T {
if (!this.cache.has(key)) {
this.cache.set(key, factory());
}
return this.cache.get(key);
}
}
// 2. 批量更新 UI
class ThemeUIUpdater {
private updateQueue: (() => void)[] = [];
private updateScheduled = false;
scheduleUpdate(updateFn: () => void): void {
this.updateQueue.push(updateFn);
if (!this.updateScheduled) {
this.updateScheduled = true;
setImmediate(() => {
this.flushUpdates();
});
}
}
private flushUpdates(): void {
const updates = this.updateQueue.splice(0);
updates.forEach(update => {
try {
update();
} catch (error) {
console.error('Theme update error:', error);
}
});
this.updateScheduled = false;
}
}错误处理
typescript
class ThemeErrorHandler {
static handleThemeError(error: Error, context: string): void {
console.error(`Theme error in ${context}:`, error);
// 记录错误
this.logError(error, context);
// 尝试恢复
this.attemptRecovery(context);
}
private static logError(error: Error, context: string): void {
// 发送错误报告
// telemetry.sendError('theme-error', { context, error: error.message });
}
private static attemptRecovery(context: string): void {
switch (context) {
case 'theme-switch':
// 尝试切换到默认主题
commands.executeCommand('workbench.action.selectTheme', 'Default Dark+');
break;
case 'webview-theme':
// 重新发送主题数据
// webviewThemeProvider.updateAllWebviews();
break;
}
}
}相关 API
- 窗口 API - 窗口管理和事件
- 配置 API - 配置管理
- 命令 API - 命令执行
- Webview API - Webview 集成