扩展开发指南
本指南介绍如何为 Trae IDE 开发扩展,包括扩展架构、API 使用、最佳实践和发布流程。
概述
Trae IDE 扩展系统允许开发者:
- 添加新的编辑器功能
- 集成外部工具和服务
- 自定义用户界面
- 扩展语言支持
- 创建主题和图标包
- 实现自定义调试器
扩展架构
扩展类型
语言扩展
json
{
"name": "my-language-support",
"displayName": "My Language Support",
"description": "Language support for MyLang",
"version": "1.0.0",
"engines": {
"trae": "^1.0.0"
},
"categories": ["Programming Languages"],
"contributes": {
"languages": [{
"id": "mylang",
"aliases": ["MyLang", "mylang"],
"extensions": [".ml", ".mylang"],
"configuration": "./language-configuration.json"
}],
"grammars": [{
"language": "mylang",
"scopeName": "source.mylang",
"path": "./syntaxes/mylang.tmGrammar.json"
}]
}
}主题扩展
json
{
"name": "my-theme",
"displayName": "My Beautiful Theme",
"description": "A beautiful dark theme",
"version": "1.0.0",
"engines": {
"trae": "^1.0.0"
},
"categories": ["Themes"],
"contributes": {
"themes": [{
"label": "My Beautiful Dark",
"uiTheme": "vs-dark",
"path": "./themes/my-beautiful-dark.json"
}]
}
}功能扩展
json
{
"name": "my-extension",
"displayName": "My Extension",
"description": "Adds awesome functionality",
"version": "1.0.0",
"engines": {
"trae": "^1.0.0"
},
"categories": ["Other"],
"activationEvents": [
"onCommand:myExtension.helloWorld",
"onLanguage:javascript"
],
"main": "./out/extension.js",
"contributes": {
"commands": [{
"command": "myExtension.helloWorld",
"title": "Hello World"
}],
"menus": {
"commandPalette": [{
"command": "myExtension.helloWorld",
"when": "editorTextFocus"
}]
}
}
}扩展清单 (package.json)
json
{
"name": "my-extension",
"displayName": "My Extension",
"description": "Extension description",
"version": "1.0.0",
"publisher": "your-publisher-name",
"engines": {
"trae": "^1.0.0"
},
"categories": [
"Programming Languages",
"Themes",
"Debuggers",
"Formatters",
"Linters",
"Snippets",
"Keymaps",
"Other"
],
"keywords": [
"javascript",
"typescript",
"react"
],
"activationEvents": [
"onCommand:myExtension.activate",
"onLanguage:javascript",
"onFileSystem:sftp",
"workspaceContains:**/.eslintrc.*",
"*"
],
"main": "./out/extension.js",
"contributes": {
"commands": [],
"menus": {},
"keybindings": [],
"languages": [],
"grammars": [],
"themes": [],
"snippets": [],
"configuration": {},
"views": {},
"viewsContainers": {},
"problemMatchers": [],
"debuggers": []
},
"scripts": {
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"package": "trae package",
"publish": "trae publish"
},
"devDependencies": {
"@types/trae": "^1.0.0",
"typescript": "^4.0.0"
}
}开发环境设置
安装开发工具
bash
# 安装 Trae 扩展开发工具
npm install -g @trae/extension-generator
npm install -g @trae/extension-manager
# 创建新扩展
trae-ext create my-extension
cd my-extension
# 安装依赖
npm install项目结构
my-extension/
├── .trae/
│ └── launch.json # 调试配置
├── src/
│ ├── extension.ts # 主扩展文件
│ ├── commands/
│ │ └── helloWorld.ts
│ ├── providers/
│ │ ├── completionProvider.ts
│ │ └── hoverProvider.ts
│ └── utils/
│ └── helpers.ts
├── syntaxes/
│ └── mylang.tmGrammar.json
├── themes/
│ └── my-theme.json
├── snippets/
│ └── javascript.json
├── images/
│ └── icon.png
├── CHANGELOG.md
├── README.md
├── package.json
└── tsconfig.jsonTypeScript 配置
json
// tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "es2020",
"outDir": "out",
"lib": ["es2020"],
"sourceMap": true,
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", ".trae-test"]
}扩展 API
基本扩展结构
typescript
// src/extension.ts
import * as trae from 'trae';
export function activate(context: trae.ExtensionContext) {
console.log('Extension "my-extension" is now active!');
// 注册命令
const disposable = trae.commands.registerCommand(
'myExtension.helloWorld',
() => {
trae.window.showInformationMessage('Hello World from My Extension!');
}
);
context.subscriptions.push(disposable);
// 注册语言功能
registerLanguageFeatures(context);
// 注册视图
registerViews(context);
}
export function deactivate() {
console.log('Extension "my-extension" is now deactivated!');
}
function registerLanguageFeatures(context: trae.ExtensionContext) {
// 代码补全提供者
const completionProvider = trae.languages.registerCompletionItemProvider(
'javascript',
new MyCompletionProvider(),
'.', '"', "'"
);
// 悬停提供者
const hoverProvider = trae.languages.registerHoverProvider(
'javascript',
new MyHoverProvider()
);
context.subscriptions.push(completionProvider, hoverProvider);
}
function registerViews(context: trae.ExtensionContext) {
// 自定义视图
const provider = new MyTreeDataProvider();
trae.window.createTreeView('myExtension.myView', {
treeDataProvider: provider,
showCollapseAll: true
});
}代码补全提供者
typescript
// src/providers/completionProvider.ts
import * as trae from 'trae';
export class MyCompletionProvider implements trae.CompletionItemProvider {
provideCompletionItems(
document: trae.TextDocument,
position: trae.Position,
token: trae.CancellationToken,
context: trae.CompletionContext
): trae.ProviderResult<trae.CompletionItem[] | trae.CompletionList> {
const linePrefix = document.lineAt(position).text.substr(0, position.character);
if (!linePrefix.endsWith('console.')) {
return undefined;
}
return [
new trae.CompletionItem('log', trae.CompletionItemKind.Method),
new trae.CompletionItem('warn', trae.CompletionItemKind.Method),
new trae.CompletionItem('error', trae.CompletionItemKind.Method),
];
}
resolveCompletionItem(
item: trae.CompletionItem,
token: trae.CancellationToken
): trae.ProviderResult<trae.CompletionItem> {
if (item.label === 'log') {
item.detail = 'console.log()';
item.documentation = new trae.MarkdownString('Outputs a message to the console.');
item.insertText = new trae.SnippetString('log(${1:message})');
}
return item;
}
}悬停提供者
typescript
// src/providers/hoverProvider.ts
import * as trae from 'trae';
export class MyHoverProvider implements trae.HoverProvider {
provideHover(
document: trae.TextDocument,
position: trae.Position,
token: trae.CancellationToken
): trae.ProviderResult<trae.Hover> {
const range = document.getWordRangeAtPosition(position);
const word = document.getText(range);
if (word === 'console') {
const contents = new trae.MarkdownString();
contents.appendCodeblock('console', 'javascript');
contents.appendMarkdown('The **console** object provides access to the browser\'s debugging console.');
return new trae.Hover(contents, range);
}
return undefined;
}
}树视图提供者
typescript
// src/providers/treeDataProvider.ts
import * as trae from 'trae';
export class MyTreeDataProvider implements trae.TreeDataProvider<TreeItem> {
private _onDidChangeTreeData: trae.EventEmitter<TreeItem | undefined | null | void> = new trae.EventEmitter<TreeItem | undefined | null | void>();
readonly onDidChangeTreeData: trae.Event<TreeItem | undefined | null | void> = this._onDidChangeTreeData.event;
constructor() {}
refresh(): void {
this._onDidChangeTreeData.fire();
}
getTreeItem(element: TreeItem): trae.TreeItem {
return element;
}
getChildren(element?: TreeItem): Thenable<TreeItem[]> {
if (!element) {
return Promise.resolve([
new TreeItem('Item 1', trae.TreeItemCollapsibleState.None),
new TreeItem('Item 2', trae.TreeItemCollapsibleState.Collapsed),
new TreeItem('Item 3', trae.TreeItemCollapsibleState.None)
]);
}
return Promise.resolve([]);
}
}
class TreeItem extends trae.TreeItem {
constructor(
public readonly label: string,
public readonly collapsibleState: trae.TreeItemCollapsibleState
) {
super(label, collapsibleState);
this.tooltip = `${this.label} - Custom tooltip`;
this.description = 'Description';
}
iconPath = {
light: trae.Uri.joinPath(trae.extensions.getExtension('publisher.extension-name')!.extensionUri, 'images', 'light', 'icon.svg'),
dark: trae.Uri.joinPath(trae.extensions.getExtension('publisher.extension-name')!.extensionUri, 'images', 'dark', 'icon.svg')
};
contextValue = 'treeItem';
}语言支持
语法高亮
json
// syntaxes/mylang.tmGrammar.json
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "MyLang",
"patterns": [
{
"include": "#keywords"
},
{
"include": "#strings"
},
{
"include": "#comments"
}
],
"repository": {
"keywords": {
"patterns": [{
"name": "keyword.control.mylang",
"match": "\\b(if|while|for|return|function)\\b"
}]
},
"strings": {
"name": "string.quoted.double.mylang",
"begin": "\"",
"end": "\"",
"patterns": [
{
"name": "constant.character.escape.mylang",
"match": "\\\\."
}
]
},
"comments": {
"patterns": [
{
"name": "comment.line.double-slash.mylang",
"match": "//.*$"
},
{
"name": "comment.block.mylang",
"begin": "/\\*",
"end": "\\*/"
}
]
}
},
"scopeName": "source.mylang"
}语言配置
json
// language-configuration.json
{
"comments": {
"lineComment": "//",
"blockComment": ["/*", "*/"]
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
],
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
],
"folding": {
"markers": {
"start": "^\\s*//\\s*#region\\b",
"end": "^\\s*//\\s*#endregion\\b"
}
},
"wordPattern": "[a-zA-Z_$][a-zA-Z0-9_$]*",
"indentationRules": {
"increaseIndentPattern": "^.*\\{[^}\"']*$",
"decreaseIndentPattern": "^\\s*\\}"
}
}代码片段
json
// snippets/javascript.json
{
"Print to console": {
"prefix": "log",
"body": [
"console.log('$1');",
"$2"
],
"description": "Log output to console"
},
"Function": {
"prefix": "func",
"body": [
"function ${1:name}(${2:params}) {",
"\t$3",
"}"
],
"description": "Create a function"
},
"Arrow Function": {
"prefix": "arrow",
"body": [
"const ${1:name} = (${2:params}) => {",
"\t$3",
"};"
],
"description": "Create an arrow function"
}
}调试器扩展
调试器配置
json
// package.json - debuggers contribution
{
"contributes": {
"debuggers": [{
"type": "mylang",
"label": "MyLang Debug",
"program": "./out/debugAdapter.js",
"runtime": "node",
"configurationAttributes": {
"launch": {
"required": ["program"],
"properties": {
"program": {
"type": "string",
"description": "Absolute path to a text file.",
"default": "${workspaceFolder}/${command:AskForProgramName}"
},
"stopOnEntry": {
"type": "boolean",
"description": "Automatically stop after launch.",
"default": true
}
}
}
},
"initialConfigurations": [
{
"type": "mylang",
"request": "launch",
"name": "Ask for file name",
"program": "${workspaceFolder}/${command:AskForProgramName}",
"stopOnEntry": true
}
],
"configurationSnippets": [
{
"label": "MyLang Debug: Launch",
"description": "A new configuration for 'debugging' a user selected mylang file.",
"body": {
"type": "mylang",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/${command:AskForProgramName}"
}
}
]
}]
}
}调试适配器
typescript
// src/debugAdapter.ts
import {
DebugSession,
InitializedEvent,
TerminatedEvent,
StoppedEvent,
BreakpointEvent,
OutputEvent,
Thread,
StackFrame,
Scope,
Source,
Handles,
Breakpoint
} from 'trae-debugadapter';
import { DebugProtocol } from 'trae-debugprotocol';
import { basename } from 'path';
export class MyLangDebugSession extends DebugSession {
private static THREAD_ID = 1;
private _variableHandles = new Handles<string>();
private _configurationDone = new Subject();
public constructor() {
super();
this.setDebuggerLinesStartAt1(false);
this.setDebuggerColumnsStartAt1(false);
}
protected initializeRequest(
response: DebugProtocol.InitializeResponse,
args: DebugProtocol.InitializeRequestArguments
): void {
response.body = response.body || {};
response.body.supportsConfigurationDoneRequest = true;
response.body.supportsEvaluateForHovers = true;
response.body.supportsStepBack = false;
response.body.supportsDataBreakpoints = false;
response.body.supportsCompletionsRequest = true;
response.body.completionTriggerCharacters = [".", "["];
response.body.supportsCancelRequest = false;
response.body.supportsBreakpointLocationsRequest = true;
this.sendResponse(response);
this.sendEvent(new InitializedEvent());
}
protected configurationDoneRequest(
response: DebugProtocol.ConfigurationDoneResponse,
args: DebugProtocol.ConfigurationDoneArguments
): void {
super.configurationDoneRequest(response, args);
this._configurationDone.notify();
}
protected async launchRequest(
response: DebugProtocol.LaunchResponse,
args: ILaunchRequestArguments
) {
await this._configurationDone.wait(1000);
// 启动调试会话
this.sendResponse(response);
if (args.stopOnEntry) {
this.sendEvent(new StoppedEvent('entry', MyLangDebugSession.THREAD_ID));
} else {
this.continueRequest(<DebugProtocol.ContinueResponse>response, { threadId: MyLangDebugSession.THREAD_ID });
}
}
protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
response.body = {
threads: [
new Thread(MyLangDebugSession.THREAD_ID, "thread 1")
]
};
this.sendResponse(response);
}
protected stackTraceRequest(
response: DebugProtocol.StackTraceResponse,
args: DebugProtocol.StackTraceArguments
): void {
const frames = new Array<StackFrame>();
// 创建堆栈帧
const frame = new StackFrame(
0,
'main',
this.createSource('main.mylang'),
1,
0
);
frames.push(frame);
response.body = {
stackFrames: frames,
totalFrames: frames.length
};
this.sendResponse(response);
}
private createSource(filePath: string): Source {
return new Source(
basename(filePath),
this.convertDebuggerPathToClient(filePath),
undefined,
undefined,
'mylang-adapter-data'
);
}
}
interface ILaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
program: string;
stopOnEntry?: boolean;
}主题开发
主题文件结构
json
// themes/my-theme.json
{
"name": "My Beautiful Theme",
"type": "dark",
"colors": {
// 编辑器颜色
"editor.background": "#1e1e1e",
"editor.foreground": "#d4d4d4",
"editor.lineHighlightBackground": "#2d2d30",
"editor.selectionBackground": "#264f78",
"editor.selectionHighlightBackground": "#add6ff26",
"editor.wordHighlightBackground": "#575757b8",
"editor.wordHighlightStrongBackground": "#004972b8",
"editor.findMatchBackground": "#515c6a",
"editor.findMatchHighlightBackground": "#ea5c0055",
"editor.hoverHighlightBackground": "#264f7840",
"editor.linkActiveForeground": "#4e94ce",
"editor.rangeHighlightBackground": "#ffffff0b",
// 活动栏
"activityBar.background": "#2d2d30",
"activityBar.foreground": "#ffffff",
"activityBar.inactiveForeground": "#ffffff66",
"activityBar.border": "#2d2d30",
"activityBarBadge.background": "#007acc",
"activityBarBadge.foreground": "#ffffff",
// 侧边栏
"sideBar.background": "#252526",
"sideBar.foreground": "#cccccc",
"sideBar.border": "#2d2d30",
"sideBarTitle.foreground": "#cccccc",
"sideBarSectionHeader.background": "#2d2d30",
"sideBarSectionHeader.foreground": "#cccccc",
// 状态栏
"statusBar.background": "#007acc",
"statusBar.foreground": "#ffffff",
"statusBar.border": "#007acc",
"statusBar.debuggingBackground": "#cc6633",
"statusBar.debuggingForeground": "#ffffff",
"statusBar.noFolderBackground": "#68217a",
"statusBar.noFolderForeground": "#ffffff",
// 标签页
"tab.activeBackground": "#1e1e1e",
"tab.activeForeground": "#ffffff",
"tab.border": "#2d2d30",
"tab.activeBorder": "#007acc",
"tab.unfocusedActiveBorder": "#2d2d30",
"tab.inactiveBackground": "#2d2d30",
"tab.inactiveForeground": "#ffffff80",
"tab.unfocusedActiveForeground": "#ffffff80",
"tab.unfocusedInactiveForeground": "#ffffff40",
// 终端
"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"
},
"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": "#4EC9B0"
}
},
{
"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": "JSON Key - Level 4",
"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 meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json"
],
"settings": {
"foreground": "#C17E70"
}
},
{
"name": "JSON Key - Level 5",
"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 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": "#82AAFF"
}
},
{
"name": "JSON Key - Level 6",
"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 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": "#f07178"
}
},
{
"name": "JSON Key - Level 7",
"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 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 meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json"
],
"settings": {
"foreground": "#C792EA"
}
},
{
"name": "JSON Key - Level 8",
"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 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 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": "#C3E88D"
}
},
{
"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": "#CE9178"
}
},
{
"name": "Markdown - Markup Raw Inline Punctuation",
"scope": [
"text.html.markdown markup.inline.raw.markdown punctuation.definition.raw.markdown"
],
"settings": {
"foreground": "#D4D4D4"
}
},
{
"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": "#f07178"
}
},
{
"name": "Markup - Bold",
"scope": [
"markup.bold",
"markup.bold string"
],
"settings": {
"fontStyle": "bold",
"foreground": "#f07178"
}
},
{
"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": "#f07178"
}
},
{
"name": "Markup - Underline",
"scope": [
"markup.underline"
],
"settings": {
"fontStyle": "underline",
"foreground": "#F78C6C"
}
},
{
"name": "Markdown - Blockquote",
"scope": [
"markup.quote punctuation.definition.blockquote.markdown"
],
"settings": {
"foreground": "#FFCB6B"
}
},
{
"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": "#CE9178"
}
},
{
"name": "Markdown - Link Anchor",
"scope": [
"constant.other.reference.link.markdown"
],
"settings": {
"foreground": "#FFCB6B"
}
},
{
"name": "Markup - Raw Block",
"scope": [
"markup.raw.block"
],
"settings": {
"foreground": "#CE9178"
}
},
{
"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": "#569CD6"
}
},
{
"name": "Markdown - Separator",
"scope": [
"meta.separator"
],
"settings": {
"fontStyle": "bold",
"foreground": "#2e3440"
}
},
{
"name": "Markup - Table",
"scope": [
"markup.table"
],
"settings": {
"foreground": "#CCCCCC"
}
}
]
}测试和调试
扩展测试
typescript
// src/test/suite/extension.test.ts
import * as assert from 'assert';
import * as trae from 'trae';
import * as myExtension from '../../extension';
suite('Extension Test Suite', () => {
trae.window.showInformationMessage('Start all tests.');
test('Extension should be present', () => {
assert.ok(trae.extensions.getExtension('publisher.my-extension'));
});
test('Extension should activate', async () => {
const ext = trae.extensions.getExtension('publisher.my-extension')!;
await ext.activate();
assert.strictEqual(ext.isActive, true);
});
test('Should register hello world command', async () => {
const commands = await trae.commands.getCommands(true);
assert.ok(commands.includes('myExtension.helloWorld'));
});
test('Hello world command should show message', async () => {
// 模拟命令执行
await trae.commands.executeCommand('myExtension.helloWorld');
// 验证消息显示(需要模拟 showInformationMessage)
});
});调试配置
json
// .trae/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "${workspaceFolder}/npm: watch"
},
{
"name": "Extension Tests",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
],
"outFiles": [
"${workspaceFolder}/out/test/**/*.js"
],
"preLaunchTask": "${workspaceFolder}/npm: watch"
}
]
}任务配置
json
// .trae/tasks.json
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "npm",
"script": "compile",
"problemMatcher": "$tsc",
"group": "build"
},
{
"label": "package extension",
"type": "shell",
"command": "trae package",
"group": "build",
"dependsOn": "npm: compile"
}
]
}打包和发布
打包扩展
bash
# 安装打包工具
npm install -g @trae/trae-cli
# 编译 TypeScript
npm run compile
# 打包扩展
trae package
# 这将创建 .trex 文件
# my-extension-1.0.0.trex发布到市场
bash
# 登录到 Trae 市场
trae login
# 发布扩展
trae publish
# 或者发布特定版本
trae publish my-extension-1.0.0.trex
# 更新扩展
trae publish --update版本管理
bash
# 增加版本号
npm version patch # 1.0.0 -> 1.0.1
npm version minor # 1.0.1 -> 1.1.0
npm version major # 1.1.0 -> 2.0.0
# 发布新版本
trae publish发布配置
json
// .traerc.json
{
"publisher": "your-publisher-name",
"registry": "https://marketplace.trae.dev",
"token": "your-access-token",
"categories": [
"Programming Languages",
"Themes",
"Debuggers"
],
"badges": [
{
"url": "https://img.shields.io/badge/build-passing-brightgreen",
"href": "https://github.com/user/repo/actions",
"description": "Build Status"
}
],
"qna": "marketplace",
"sponsor": {
"url": "https://github.com/sponsors/username"
}
}最佳实践
代码质量
- 使用 TypeScript:提供类型安全和更好的开发体验
- 遵循命名约定:使用清晰的命名和一致的代码风格
- 错误处理:妥善处理异常和边界情况
- 性能优化:避免阻塞主线程,使用异步操作
用户体验
- 清晰的文档:提供详细的 README 和使用说明
- 合理的默认值:设置合理的默认配置
- 渐进式功能:允许用户逐步启用功能
- 反馈机制:提供清晰的状态反馈和错误消息
兼容性
- 版本兼容:支持多个 Trae 版本
- 平台兼容:确保在不同操作系统上正常工作
- 向后兼容:保持 API 的向后兼容性
- 依赖管理:最小化外部依赖
安全性
- 输入验证:验证所有用户输入
- 权限最小化:只请求必要的权限
- 安全存储:安全存储敏感信息
- 代码审查:定期进行安全代码审查
故障排除
常见问题
扩展无法激活
typescript
// 检查激活事件
"activationEvents": [
"onCommand:myExtension.activate",
"onLanguage:javascript"
]
// 确保主文件路径正确
"main": "./out/extension.js"
// 检查编译输出
npm run compile命令未注册
typescript
// 确保在 package.json 中声明命令
"contributes": {
"commands": [{
"command": "myExtension.helloWorld",
"title": "Hello World"
}]
}
// 在扩展代码中注册命令
const disposable = trae.commands.registerCommand(
'myExtension.helloWorld',
() => {
trae.window.showInformationMessage('Hello World!');
}
);
context.subscriptions.push(disposable);语言功能不工作
typescript
// 检查语言 ID 是否正确
"languages": [{
"id": "mylang",
"aliases": ["MyLang", "mylang"],
"extensions": [".ml", ".mylang"]
}]
// 确保提供者正确注册
const provider = trae.languages.registerCompletionItemProvider(
'mylang', // 语言 ID 必须匹配
new MyCompletionProvider()
);调试技巧
使用开发者工具
typescript
// 在扩展代码中添加调试信息
console.log('Extension activated');
console.error('Error occurred:', error);
// 使用 Trae 的输出面板
const outputChannel = trae.window.createOutputChannel('My Extension');
outputChannel.appendLine('Debug message');
outputChannel.show();性能分析
typescript
// 测量执行时间
const start = Date.now();
// 执行代码
const end = Date.now();
console.log(`Execution time: ${end - start}ms`);
// 使用性能标记
performance.mark('start-operation');
// 执行代码
performance.mark('end-operation');
performance.measure('operation', 'start-operation', 'end-operation');高级主题
自定义编辑器
typescript
// 注册自定义编辑器
export class MyCustomEditorProvider implements trae.CustomTextEditorProvider {
public static register(context: trae.ExtensionContext): trae.Disposable {
const provider = new MyCustomEditorProvider(context);
const providerRegistration = trae.window.registerCustomEditorProvider(
MyCustomEditorProvider.viewType,
provider
);
return providerRegistration;
}
private static readonly viewType = 'myExtension.customEditor';
constructor(private readonly context: trae.ExtensionContext) {}
public async resolveCustomTextEditor(
document: trae.TextDocument,
webviewPanel: trae.WebviewPanel,
_token: trae.CancellationToken
): Promise<void> {
webviewPanel.webview.options = {
enableScripts: true,
};
webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview);
function updateWebview() {
webviewPanel.webview.postMessage({
type: 'update',
text: document.getText(),
});
}
const changeDocumentSubscription = trae.workspace.onDidChangeTextDocument(e => {
if (e.document.uri.toString() === document.uri.toString()) {
updateWebview();
}
});
webviewPanel.onDidDispose(() => {
changeDocumentSubscription.dispose();
});
webviewPanel.webview.onDidReceiveMessage(e => {
switch (e.type) {
case 'add':
this.addNewScratch(document);
return;
case 'delete':
this.deleteScratch(document, e.id);
return;
}
});
updateWebview();
}
private getHtmlForWebview(webview: trae.Webview): string {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Custom Editor</title>
</head>
<body>
<div id="app"></div>
<script>
const trae = acquireTraeApi();
// 自定义编辑器逻辑
</script>
</body>
</html>`;
}
}文件系统提供者
typescript
export class MyFileSystemProvider implements trae.FileSystemProvider {
private _emitter = new trae.EventEmitter<trae.FileChangeEvent[]>();
readonly onDidChangeFile: trae.Event<trae.FileChangeEvent[]> = this._emitter.event;
watch(uri: trae.Uri, options: { recursive: boolean; excludes: string[]; }): trae.Disposable {
// 实现文件监视逻辑
return new trae.Disposable(() => {});
}
stat(uri: trae.Uri): trae.FileStat | Thenable<trae.FileStat> {
// 返回文件统计信息
return {
type: trae.FileType.File,
ctime: Date.now(),
mtime: Date.now(),
size: 0
};
}
readDirectory(uri: trae.Uri): [string, trae.FileType][] | Thenable<[string, trae.FileType][]> {
// 读取目录内容
return [];
}
createDirectory(uri: trae.Uri): void | Thenable<void> {
// 创建目录
}
readFile(uri: trae.Uri): Uint8Array | Thenable<Uint8Array> {
// 读取文件内容
return new Uint8Array();
}
writeFile(uri: trae.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable<void> {
// 写入文件内容
}
delete(uri: trae.Uri, options: { recursive: boolean; }): void | Thenable<void> {
// 删除文件或目录
}
rename(oldUri: trae.Uri, newUri: trae.Uri, options: { overwrite: boolean; }): void | Thenable<void> {
// 重命名文件或目录
}
}
// 注册文件系统提供者
const provider = new MyFileSystemProvider();
context.subscriptions.push(
trae.workspace.registerFileSystemProvider('myscheme', provider, {
isCaseSensitive: true
})
);社区和资源
官方资源
社区资源
学习资源
通过本指南,您应该能够开始开发自己的 Trae IDE 扩展。记住要遵循最佳实践,编写高质量的代码,并为用户提供良好的体验。如果遇到问题,不要犹豫向社区寻求帮助!