插件 API
概述
Trae 插件 API 提供了强大的扩展机制,允许开发者创建、管理和分发插件。通过插件系统,可以扩展 IDE 的功能,添加新的语言支持、工具集成、UI 组件等。
核心概念
插件结构
typescript
interface Plugin {
readonly id: string;
readonly name: string;
readonly version: string;
readonly description?: string;
readonly author?: string;
readonly license?: string;
readonly homepage?: string;
readonly repository?: string;
readonly keywords?: string[];
readonly engines: EngineRequirement;
readonly dependencies?: Record<string, string>;
readonly devDependencies?: Record<string, string>;
readonly activationEvents?: string[];
readonly main?: string;
readonly contributes?: PluginContributions;
}
interface EngineRequirement {
trae: string; // 例如: "^1.0.0"
node?: string;
}
interface PluginContributions {
commands?: CommandContribution[];
menus?: MenuContribution[];
keybindings?: KeybindingContribution[];
languages?: LanguageContribution[];
grammars?: GrammarContribution[];
themes?: ThemeContribution[];
iconThemes?: IconThemeContribution[];
snippets?: SnippetContribution[];
debuggers?: DebuggerContribution[];
configuration?: ConfigurationContribution;
views?: ViewContribution[];
viewsContainers?: ViewContainerContribution[];
problemMatchers?: ProblemMatcherContribution[];
taskDefinitions?: TaskDefinitionContribution[];
}插件生命周期
typescript
interface PluginContext {
readonly subscriptions: Disposable[];
readonly workspaceState: Memento;
readonly globalState: Memento;
readonly extensionPath: string;
readonly extensionUri: Uri;
readonly storageUri?: Uri;
readonly globalStorageUri: Uri;
readonly logUri: Uri;
readonly extensionMode: ExtensionMode;
readonly environmentVariableCollection: EnvironmentVariableCollection;
}
enum ExtensionMode {
Production = 1,
Development = 2,
Test = 3
}
// 插件激活函数
export function activate(context: PluginContext): void | Thenable<void> {
// 插件激活逻辑
}
// 插件停用函数
export function deactivate(): void | Thenable<void> {
// 插件清理逻辑
}API 参考
插件管理
插件注册
typescript
import { plugins } from '@trae/api';
// 注册插件
const registerPlugin = (plugin: Plugin): Disposable => {
return plugins.registerPlugin(plugin);
};
// 获取插件信息
const getPlugin = (id: string): Plugin | undefined => {
return plugins.getPlugin(id);
};
// 获取所有插件
const getAllPlugins = (): Plugin[] => {
return plugins.getAllPlugins();
};
// 检查插件是否已激活
const isPluginActive = (id: string): boolean => {
return plugins.isActive(id);
};
// 激活插件
const activatePlugin = async (id: string): Promise<void> => {
await plugins.activate(id);
};
// 停用插件
const deactivatePlugin = async (id: string): Promise<void> => {
await plugins.deactivate(id);
};插件依赖管理
typescript
class PluginDependencyManager {
private dependencyGraph: Map<string, Set<string>> = new Map();
private reverseDependencyGraph: Map<string, Set<string>> = new Map();
// 添加依赖关系
addDependency(pluginId: string, dependencyId: string): void {
if (!this.dependencyGraph.has(pluginId)) {
this.dependencyGraph.set(pluginId, new Set());
}
this.dependencyGraph.get(pluginId)!.add(dependencyId);
if (!this.reverseDependencyGraph.has(dependencyId)) {
this.reverseDependencyGraph.set(dependencyId, new Set());
}
this.reverseDependencyGraph.get(dependencyId)!.add(pluginId);
}
// 获取插件依赖
getDependencies(pluginId: string): string[] {
return Array.from(this.dependencyGraph.get(pluginId) || []);
}
// 获取依赖此插件的插件
getDependents(pluginId: string): string[] {
return Array.from(this.reverseDependencyGraph.get(pluginId) || []);
}
// 检查循环依赖
hasCyclicDependency(pluginId: string): boolean {
const visited = new Set<string>();
const recursionStack = new Set<string>();
return this.detectCycle(pluginId, visited, recursionStack);
}
private detectCycle(
pluginId: string,
visited: Set<string>,
recursionStack: Set<string>
): boolean {
visited.add(pluginId);
recursionStack.add(pluginId);
const dependencies = this.dependencyGraph.get(pluginId) || new Set();
for (const dependency of dependencies) {
if (!visited.has(dependency)) {
if (this.detectCycle(dependency, visited, recursionStack)) {
return true;
}
} else if (recursionStack.has(dependency)) {
return true;
}
}
recursionStack.delete(pluginId);
return false;
}
// 获取激活顺序
getActivationOrder(pluginIds: string[]): string[] {
const result: string[] = [];
const visited = new Set<string>();
const temp = new Set<string>();
const visit = (pluginId: string): void => {
if (temp.has(pluginId)) {
throw new Error(`Circular dependency detected: ${pluginId}`);
}
if (!visited.has(pluginId)) {
temp.add(pluginId);
const dependencies = this.dependencyGraph.get(pluginId) || new Set();
for (const dependency of dependencies) {
visit(dependency);
}
temp.delete(pluginId);
visited.add(pluginId);
result.push(pluginId);
}
};
for (const pluginId of pluginIds) {
visit(pluginId);
}
return result;
}
}插件开发
基础插件模板
typescript
// package.json
{
"name": "my-awesome-plugin",
"displayName": "My Awesome Plugin",
"description": "An awesome plugin for Trae",
"version": "1.0.0",
"engines": {
"trae": "^1.0.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onCommand:myAwesomePlugin.helloWorld"
],
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "myAwesomePlugin.helloWorld",
"title": "Hello World",
"category": "My Awesome Plugin"
}
],
"menus": {
"commandPalette": [
{
"command": "myAwesomePlugin.helloWorld",
"when": "true"
}
]
}
},
"scripts": {
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./"
},
"devDependencies": {
"@types/trae": "^1.0.0",
"typescript": "^4.0.0"
}
}
// src/extension.ts
import * as trae from '@trae/api';
export function activate(context: trae.PluginContext) {
console.log('My Awesome Plugin is now active!');
// 注册命令
const disposable = trae.commands.registerCommand(
'myAwesomePlugin.helloWorld',
() => {
trae.window.showInformationMessage('Hello World from My Awesome Plugin!');
}
);
context.subscriptions.push(disposable);
}
export function deactivate() {
console.log('My Awesome Plugin is now deactivated!');
}高级插件示例
typescript
// 语言支持插件
class LanguageSupportPlugin {
private disposables: trae.Disposable[] = [];
activate(context: trae.PluginContext): void {
// 注册语言
this.disposables.push(
trae.languages.registerLanguage({
id: 'mylang',
extensions: ['.mylang'],
aliases: ['MyLang'],
mimetypes: ['text/x-mylang']
})
);
// 注册语言配置
this.disposables.push(
trae.languages.setLanguageConfiguration('mylang', {
comments: {
lineComment: '//',
blockComment: ['/*', '*/']
},
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
autoClosingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' }
]
})
);
// 注册补全提供者
this.disposables.push(
trae.languages.registerCompletionItemProvider(
{ language: 'mylang' },
new MyLangCompletionProvider(),
'.', '('
)
);
// 注册诊断提供者
const diagnosticCollection = trae.languages.createDiagnosticCollection('mylang');
this.disposables.push(diagnosticCollection);
// 监听文档变化
this.disposables.push(
trae.workspace.onDidChangeTextDocument(event => {
if (event.document.languageId === 'mylang') {
this.validateDocument(event.document, diagnosticCollection);
}
})
);
context.subscriptions.push(...this.disposables);
}
private async validateDocument(
document: trae.TextDocument,
diagnosticCollection: trae.DiagnosticCollection
): Promise<void> {
const diagnostics: trae.Diagnostic[] = [];
const text = document.getText();
// 简单的语法检查
const lines = text.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// 检查未闭合的括号
const openBrackets = (line.match(/\{/g) || []).length;
const closeBrackets = (line.match(/\}/g) || []).length;
if (openBrackets !== closeBrackets) {
diagnostics.push({
range: new trae.Range(
new trae.Position(i, 0),
new trae.Position(i, line.length)
),
message: 'Unmatched brackets',
severity: trae.DiagnosticSeverity.Error
});
}
}
diagnosticCollection.set(document.uri, diagnostics);
}
deactivate(): void {
this.disposables.forEach(d => d.dispose());
}
}
class MyLangCompletionProvider implements trae.CompletionItemProvider {
provideCompletionItems(
document: trae.TextDocument,
position: trae.Position,
token: trae.CancellationToken,
context: trae.CompletionContext
): trae.CompletionItem[] {
const items: trae.CompletionItem[] = [];
// 添加关键字补全
const keywords = ['function', 'class', 'if', 'else', 'for', 'while'];
for (const keyword of keywords) {
items.push({
label: keyword,
kind: trae.CompletionItemKind.Keyword,
insertText: keyword
});
}
// 添加代码片段
items.push({
label: 'function',
kind: trae.CompletionItemKind.Snippet,
insertText: new trae.SnippetString('function ${1:name}(${2:params}) {\n\t$0\n}'),
documentation: 'Function declaration'
});
return items;
}
}插件配置
配置贡献
typescript
// package.json 中的配置贡献
{
"contributes": {
"configuration": {
"title": "My Awesome Plugin",
"properties": {
"myAwesomePlugin.enable": {
"type": "boolean",
"default": true,
"description": "Enable My Awesome Plugin"
},
"myAwesomePlugin.maxItems": {
"type": "number",
"default": 10,
"minimum": 1,
"maximum": 100,
"description": "Maximum number of items to show"
},
"myAwesomePlugin.customPath": {
"type": "string",
"default": "",
"description": "Custom path for plugin operations"
},
"myAwesomePlugin.features": {
"type": "object",
"properties": {
"autoComplete": {
"type": "boolean",
"default": true,
"description": "Enable auto completion"
},
"diagnostics": {
"type": "boolean",
"default": true,
"description": "Enable diagnostics"
}
},
"default": {
"autoComplete": true,
"diagnostics": true
},
"description": "Feature toggles"
}
}
}
}
}
// 在插件中使用配置
class PluginConfiguration {
private static readonly SECTION = 'myAwesomePlugin';
static get enabled(): boolean {
return trae.workspace.getConfiguration(this.SECTION).get('enable', true);
}
static get maxItems(): number {
return trae.workspace.getConfiguration(this.SECTION).get('maxItems', 10);
}
static get customPath(): string {
return trae.workspace.getConfiguration(this.SECTION).get('customPath', '');
}
static get features(): { autoComplete: boolean; diagnostics: boolean } {
return trae.workspace.getConfiguration(this.SECTION).get('features', {
autoComplete: true,
diagnostics: true
});
}
static onDidChange(listener: () => void): trae.Disposable {
return trae.workspace.onDidChangeConfiguration(event => {
if (event.affectsConfiguration(this.SECTION)) {
listener();
}
});
}
}
// 使用配置
class ConfigurablePlugin {
private disposables: trae.Disposable[] = [];
activate(context: trae.PluginContext): void {
// 监听配置变化
this.disposables.push(
PluginConfiguration.onDidChange(() => {
this.updateFeatures();
})
);
this.updateFeatures();
context.subscriptions.push(...this.disposables);
}
private updateFeatures(): void {
if (!PluginConfiguration.enabled) {
this.disableAllFeatures();
return;
}
const features = PluginConfiguration.features;
if (features.autoComplete) {
this.enableAutoComplete();
} else {
this.disableAutoComplete();
}
if (features.diagnostics) {
this.enableDiagnostics();
} else {
this.disableDiagnostics();
}
}
private enableAutoComplete(): void {
// 启用自动补全功能
}
private disableAutoComplete(): void {
// 禁用自动补全功能
}
private enableDiagnostics(): void {
// 启用诊断功能
}
private disableDiagnostics(): void {
// 禁用诊断功能
}
private disableAllFeatures(): void {
this.disableAutoComplete();
this.disableDiagnostics();
}
}插件通信
插件间通信
typescript
// 插件通信接口
interface PluginMessage {
type: string;
payload: any;
sender: string;
timestamp: number;
}
class PluginCommunicationManager {
private messageHandlers: Map<string, Set<(message: PluginMessage) => void>> = new Map();
private plugins: Map<string, Plugin> = new Map();
// 注册插件
registerPlugin(plugin: Plugin): void {
this.plugins.set(plugin.id, plugin);
}
// 发送消息
sendMessage(
fromPluginId: string,
toPluginId: string,
type: string,
payload: any
): void {
const message: PluginMessage = {
type,
payload,
sender: fromPluginId,
timestamp: Date.now()
};
const handlers = this.messageHandlers.get(toPluginId);
if (handlers) {
handlers.forEach(handler => {
try {
handler(message);
} catch (error) {
console.error(`Error handling message in plugin ${toPluginId}:`, error);
}
});
}
}
// 广播消息
broadcastMessage(
fromPluginId: string,
type: string,
payload: any
): void {
const message: PluginMessage = {
type,
payload,
sender: fromPluginId,
timestamp: Date.now()
};
for (const [pluginId, handlers] of this.messageHandlers) {
if (pluginId !== fromPluginId) {
handlers.forEach(handler => {
try {
handler(message);
} catch (error) {
console.error(`Error handling broadcast in plugin ${pluginId}:`, error);
}
});
}
}
}
// 订阅消息
subscribeToMessages(
pluginId: string,
handler: (message: PluginMessage) => void
): trae.Disposable {
if (!this.messageHandlers.has(pluginId)) {
this.messageHandlers.set(pluginId, new Set());
}
this.messageHandlers.get(pluginId)!.add(handler);
return {
dispose: () => {
const handlers = this.messageHandlers.get(pluginId);
if (handlers) {
handlers.delete(handler);
if (handlers.size === 0) {
this.messageHandlers.delete(pluginId);
}
}
}
};
}
// 获取可用插件列表
getAvailablePlugins(): Plugin[] {
return Array.from(this.plugins.values());
}
// 检查插件是否存在
hasPlugin(pluginId: string): boolean {
return this.plugins.has(pluginId);
}
}
// 使用插件通信
class CommunicatingPlugin {
private communicationManager: PluginCommunicationManager;
private disposables: trae.Disposable[] = [];
constructor(
private pluginId: string,
communicationManager: PluginCommunicationManager
) {
this.communicationManager = communicationManager;
}
activate(context: trae.PluginContext): void {
// 订阅消息
this.disposables.push(
this.communicationManager.subscribeToMessages(
this.pluginId,
this.handleMessage.bind(this)
)
);
// 注册命令
this.disposables.push(
trae.commands.registerCommand(
`${this.pluginId}.sendMessage`,
this.sendTestMessage.bind(this)
)
);
context.subscriptions.push(...this.disposables);
}
private handleMessage(message: PluginMessage): void {
switch (message.type) {
case 'greeting':
this.handleGreeting(message);
break;
case 'data-request':
this.handleDataRequest(message);
break;
case 'notification':
this.handleNotification(message);
break;
default:
console.log(`Unknown message type: ${message.type}`);
}
}
private handleGreeting(message: PluginMessage): void {
console.log(`Received greeting from ${message.sender}: ${message.payload.text}`);
// 回复问候
this.communicationManager.sendMessage(
this.pluginId,
message.sender,
'greeting-reply',
{ text: `Hello back from ${this.pluginId}!` }
);
}
private handleDataRequest(message: PluginMessage): void {
const requestedData = this.getRequestedData(message.payload.dataType);
this.communicationManager.sendMessage(
this.pluginId,
message.sender,
'data-response',
{ data: requestedData }
);
}
private handleNotification(message: PluginMessage): void {
trae.window.showInformationMessage(
`Notification from ${message.sender}: ${message.payload.message}`
);
}
private sendTestMessage(): void {
// 发送测试消息给所有插件
this.communicationManager.broadcastMessage(
this.pluginId,
'greeting',
{ text: 'Hello from test plugin!' }
);
}
private getRequestedData(dataType: string): any {
// 根据数据类型返回相应数据
switch (dataType) {
case 'config':
return { setting1: 'value1', setting2: 'value2' };
case 'stats':
return { activeFiles: 5, totalLines: 1000 };
default:
return null;
}
}
}插件测试
单元测试
typescript
// test/extension.test.ts
import * as assert from 'assert';
import * as trae from '@trae/api';
import { activate, deactivate } from '../src/extension';
suite('Extension Test Suite', () => {
let context: trae.PluginContext;
setup(() => {
// 创建模拟上下文
context = {
subscriptions: [],
workspaceState: new MockMemento(),
globalState: new MockMemento(),
extensionPath: '/mock/path',
extensionUri: trae.Uri.file('/mock/path'),
globalStorageUri: trae.Uri.file('/mock/global'),
logUri: trae.Uri.file('/mock/log'),
extensionMode: trae.ExtensionMode.Test,
environmentVariableCollection: new MockEnvironmentVariableCollection()
} as trae.PluginContext;
});
teardown(() => {
// 清理
context.subscriptions.forEach(d => d.dispose());
});
test('Extension should activate without errors', async () => {
await activate(context);
assert.ok(context.subscriptions.length > 0, 'Should register disposables');
});
test('Commands should be registered', async () => {
await activate(context);
const commands = await trae.commands.getCommands();
assert.ok(
commands.includes('myAwesomePlugin.helloWorld'),
'Hello World command should be registered'
);
});
test('Configuration should be accessible', () => {
const config = trae.workspace.getConfiguration('myAwesomePlugin');
assert.ok(config, 'Configuration should be accessible');
});
test('Extension should deactivate cleanly', async () => {
await activate(context);
await deactivate();
// 验证清理是否正确
});
});
// 模拟类
class MockMemento implements trae.Memento {
private storage: Map<string, any> = new Map();
get<T>(key: string): T | undefined;
get<T>(key: string, defaultValue: T): T;
get<T>(key: string, defaultValue?: T): T | undefined {
return this.storage.get(key) ?? defaultValue;
}
async update(key: string, value: any): Promise<void> {
this.storage.set(key, value);
}
keys(): readonly string[] {
return Array.from(this.storage.keys());
}
}
class MockEnvironmentVariableCollection implements trae.EnvironmentVariableCollection {
private variables: Map<string, trae.EnvironmentVariableMutator> = new Map();
replace(variable: string, value: string): void {
this.variables.set(variable, { value, type: trae.EnvironmentVariableMutatorType.Replace });
}
append(variable: string, value: string): void {
this.variables.set(variable, { value, type: trae.EnvironmentVariableMutatorType.Append });
}
prepend(variable: string, value: string): void {
this.variables.set(variable, { value, type: trae.EnvironmentVariableMutatorType.Prepend });
}
get(variable: string): trae.EnvironmentVariableMutator | undefined {
return this.variables.get(variable);
}
forEach(callback: (variable: string, mutator: trae.EnvironmentVariableMutator) => void): void {
this.variables.forEach((mutator, variable) => callback(variable, mutator));
}
delete(variable: string): void {
this.variables.delete(variable);
}
clear(): void {
this.variables.clear();
}
}集成测试
typescript
// test/integration.test.ts
import * as assert from 'assert';
import * as trae from '@trae/api';
import * as path from 'path';
suite('Integration Test Suite', () => {
let workspaceUri: trae.Uri;
suiteSetup(async () => {
// 创建测试工作区
const testWorkspace = path.join(__dirname, '..', 'test-workspace');
workspaceUri = trae.Uri.file(testWorkspace);
// 打开工作区
await trae.commands.executeCommand('trae.openFolder', workspaceUri);
});
test('Plugin should work with real files', async () => {
// 创建测试文件
const testFile = trae.Uri.joinPath(workspaceUri, 'test.mylang');
const content = 'function test() {\n console.log("Hello");\n}';
await trae.workspace.fs.writeFile(testFile, Buffer.from(content));
// 打开文件
const document = await trae.workspace.openTextDocument(testFile);
await trae.window.showTextDocument(document);
// 等待插件处理
await new Promise(resolve => setTimeout(resolve, 1000));
// 验证诊断
const diagnostics = trae.languages.getDiagnostics(testFile);
assert.ok(Array.isArray(diagnostics), 'Diagnostics should be available');
});
test('Commands should work in real environment', async () => {
// 执行插件命令
await trae.commands.executeCommand('myAwesomePlugin.helloWorld');
// 验证命令执行结果
// 这里可能需要检查状态栏、通知等
});
test('Configuration changes should be handled', async () => {
const config = trae.workspace.getConfiguration('myAwesomePlugin');
// 更改配置
await config.update('enable', false, trae.ConfigurationTarget.Workspace);
// 等待配置变化处理
await new Promise(resolve => setTimeout(resolve, 500));
// 验证配置变化的影响
const newConfig = trae.workspace.getConfiguration('myAwesomePlugin');
assert.strictEqual(newConfig.get('enable'), false);
// 恢复配置
await config.update('enable', true, trae.ConfigurationTarget.Workspace);
});
});插件发布
打包配置
typescript
// webpack.config.js
const path = require('path');
module.exports = {
target: 'node',
entry: './src/extension.ts',
output: {
path: path.resolve(__dirname, 'out'),
filename: 'extension.js',
libraryTarget: 'commonjs2',
devtoolModuleFilenameTemplate: '../[resource-path]'
},
devtool: 'source-map',
externals: {
'@trae/api': 'commonjs @trae/api'
},
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader'
}
]
}
]
}
};
// .vscodeignore
node_modules
src
test
.gitignore
.git
*.map
*.ts
.vscode-test
.nyc_output
coverage
// 发布脚本
{
"scripts": {
"package": "trae-cli package",
"publish": "trae-cli publish",
"prepublish": "npm run compile && npm run test"
}
}最佳实践
性能优化
typescript
// 1. 延迟加载
class LazyLoadingPlugin {
private featureModules: Map<string, () => Promise<any>> = new Map();
activate(context: trae.PluginContext): void {
// 注册延迟加载的功能模块
this.featureModules.set('completion', () => import('./features/completion'));
this.featureModules.set('diagnostics', () => import('./features/diagnostics'));
this.featureModules.set('formatting', () => import('./features/formatting'));
// 只在需要时加载功能
this.registerLazyCommands(context);
}
private registerLazyCommands(context: trae.PluginContext): void {
context.subscriptions.push(
trae.commands.registerCommand('plugin.enableCompletion', async () => {
const module = await this.loadFeature('completion');
module.activate(context);
})
);
}
private async loadFeature(name: string): Promise<any> {
const loader = this.featureModules.get(name);
if (!loader) {
throw new Error(`Feature ${name} not found`);
}
return await loader();
}
}
// 2. 资源管理
class ResourceManager {
private disposables: trae.Disposable[] = [];
private timers: Set<NodeJS.Timeout> = new Set();
private watchers: Set<trae.FileSystemWatcher> = new Set();
addDisposable(disposable: trae.Disposable): void {
this.disposables.push(disposable);
}
addTimer(timer: NodeJS.Timeout): void {
this.timers.add(timer);
}
addWatcher(watcher: trae.FileSystemWatcher): void {
this.watchers.add(watcher);
this.addDisposable(watcher);
}
dispose(): void {
// 清理所有资源
this.disposables.forEach(d => d.dispose());
this.timers.forEach(t => clearTimeout(t));
this.watchers.forEach(w => w.dispose());
this.disposables = [];
this.timers.clear();
this.watchers.clear();
}
}
// 3. 缓存策略
class CacheManager<T> {
private cache: Map<string, { value: T; timestamp: number }> = new Map();
private ttl: number;
constructor(ttlMs: number = 300000) { // 5分钟默认TTL
this.ttl = ttlMs;
}
get(key: string): T | undefined {
const entry = this.cache.get(key);
if (!entry) {
return undefined;
}
if (Date.now() - entry.timestamp > this.ttl) {
this.cache.delete(key);
return undefined;
}
return entry.value;
}
set(key: string, value: T): void {
this.cache.set(key, {
value,
timestamp: Date.now()
});
}
clear(): void {
this.cache.clear();
}
cleanup(): void {
const now = Date.now();
for (const [key, entry] of this.cache) {
if (now - entry.timestamp > this.ttl) {
this.cache.delete(key);
}
}
}
}错误处理
typescript
// 插件错误处理
class PluginErrorHandler {
static handleError(error: Error, context: string): void {
console.error(`Plugin error in ${context}:`, error);
// 发送错误报告
this.reportError(error, context);
// 显示用户友好的错误消息
trae.window.showErrorMessage(
`插件错误: ${error.message}`,
'报告问题',
'忽略'
).then(action => {
if (action === '报告问题') {
this.openIssueReporter(error, context);
}
});
}
static wrapAsync<T>(
operation: () => Promise<T>,
context: string
): Promise<T | undefined> {
return operation().catch(error => {
this.handleError(error, context);
return undefined;
});
}
static wrapSync<T>(
operation: () => T,
context: string
): T | undefined {
try {
return operation();
} catch (error) {
this.handleError(error as Error, context);
return undefined;
}
}
private static reportError(error: Error, context: string): void {
// 实现错误报告逻辑
const errorReport = {
message: error.message,
stack: error.stack,
context,
timestamp: new Date().toISOString(),
version: trae.version
};
// 发送到错误收集服务
console.log('Error report:', errorReport);
}
private static openIssueReporter(error: Error, context: string): void {
const issueBody = `
**Error Context:** ${context}
**Error Message:** ${error.message}
**Stack Trace:**
\`\`\`
${error.stack}
\`\`\`
**Trae Version:** ${trae.version}
`.trim();
const issueUrl = `https://github.com/your-plugin/issues/new?body=${encodeURIComponent(issueBody)}`;
trae.env.openExternal(trae.Uri.parse(issueUrl));
}
}相关 API
示例项目
查看 插件 API 示例 了解完整的实现示例。