Extensions API
Extensions APIは、Trae IDE拡張機能の作成、管理、相互作用のための包括的な機能を提供します。このAPIを使用すると、開発体験を向上させる強力な拡張機能を構築できます。
概要
Extensions APIでは以下のことができます:
- 拡張機能のライフサイクルの作成と管理
- コマンド、メニュー、キーバインディングの登録
- カスタムビューとパネルでUIに貢献
- ワークスペース設定へのアクセスと変更
- 他の拡張機能との相互作用
- 拡張機能のアクティベーションと非アクティベーションの処理
コアインターフェース
Extension
Trae IDEの拡張機能を表します。
typescript
interface Extension<T = any> {
readonly id: string;
readonly extensionUri: Uri;
readonly extensionPath: string;
readonly isActive: boolean;
readonly packageJSON: any;
readonly exports: T;
activate(): Thenable<T>;
}ExtensionContext
拡張機能のユーティリティと状態を提供します。
typescript
interface ExtensionContext {
readonly subscriptions: Disposable[];
readonly workspaceState: Memento;
readonly globalState: Memento & {
setKeysForSync(keys: readonly string[]): void;
};
readonly secrets: SecretStorage;
readonly extensionUri: Uri;
readonly extensionPath: string;
readonly environmentVariableCollection: EnvironmentVariableCollection;
readonly storageUri?: Uri;
readonly globalStorageUri: Uri;
readonly logUri: Uri;
readonly extensionMode: ExtensionMode;
asAbsolutePath(relativePath: string): string;
}拡張機能のライフサイクル
アクティベーション
拡張機能は特定の条件が満たされたときにアクティベートされます。
typescript
// package.jsonのアクティベーションイベント
{
"activationEvents": [
"onLanguage:typescript",
"onCommand:myExtension.helloWorld",
"onDebug",
"onFileSystem:sftp",
"workspaceContains:**/.eslintrc.*",
"onStartupFinished"
]
}
// 拡張機能のエントリーポイント
export function activate(context: TraeAPI.ExtensionContext) {
console.log('Extension "my-extension" is now active!');
// コマンドを登録
const disposable = TraeAPI.commands.registerCommand('myExtension.helloWorld', () => {
TraeAPI.window.showInformationMessage('Hello World from My Extension!');
});
context.subscriptions.push(disposable);
// パブリックAPIを返す
return {
sayHello() {
return 'Hello from my extension!';
}
};
}
export function deactivate() {
console.log('Extension "my-extension" is now deactivated!');
}Extension Modes
typescript
enum ExtensionMode {
Production = 1,
Development = 2,
Test = 3
}
// Check extension mode
if (context.extensionMode === TraeAPI.ExtensionMode.Development) {
console.log('Running in development mode');
}Commands
Registering Commands
typescript
// Simple command
const disposable1 = TraeAPI.commands.registerCommand('myExtension.simpleCommand', () => {
TraeAPI.window.showInformationMessage('Simple command executed!');
});
// Command with arguments
const disposable2 = TraeAPI.commands.registerCommand(
'myExtension.commandWithArgs',
(arg1: string, arg2: number) => {
TraeAPI.window.showInformationMessage(`Args: ${arg1}, ${arg2}`);
}
);
// Text editor command
const disposable3 = TraeAPI.commands.registerTextEditorCommand(
'myExtension.editorCommand',
(textEditor: TraeAPI.TextEditor, edit: TraeAPI.TextEditorEdit) => {
edit.insert(textEditor.selection.active, 'Hello from editor command!');
}
);
context.subscriptions.push(disposable1, disposable2, disposable3);Executing Commands
typescript
// Execute built-in commands
await TraeAPI.commands.executeCommand('workbench.action.files.save');
await TraeAPI.commands.executeCommand('editor.action.formatDocument');
// Execute custom commands
await TraeAPI.commands.executeCommand('myExtension.commandWithArgs', 'hello', 42);
// Get all available commands
const commands = await TraeAPI.commands.getCommands();
console.log('Available commands:', commands);UI Contributions
Status Bar
typescript
// Create status bar item
const statusBarItem = TraeAPI.window.createStatusBarItem(
TraeAPI.StatusBarAlignment.Right,
100
);
statusBarItem.text = '$(sync~spin) Processing...';
statusBarItem.tooltip = 'Click to cancel';
statusBarItem.command = 'myExtension.cancelProcessing';
statusBarItem.show();
context.subscriptions.push(statusBarItem);
// Update status bar dynamically
function updateStatus(message: string, isLoading: boolean = false) {
statusBarItem.text = isLoading ? `$(sync~spin) ${message}` : message;
statusBarItem.show();
}Tree Views
typescript
// Define tree data provider
class MyTreeDataProvider implements TraeAPI.TreeDataProvider<TreeItem> {
private _onDidChangeTreeData = new TraeAPI.EventEmitter<TreeItem | undefined | null | void>();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
getTreeItem(element: TreeItem): TraeAPI.TreeItem {
return element;
}
getChildren(element?: TreeItem): Thenable<TreeItem[]> {
if (!element) {
// Root items
return Promise.resolve([
new TreeItem('Item 1', TraeAPI.TreeItemCollapsibleState.Collapsed),
new TreeItem('Item 2', TraeAPI.TreeItemCollapsibleState.None)
]);
} else {
// Child items
return Promise.resolve([
new TreeItem('Child 1', TraeAPI.TreeItemCollapsibleState.None),
new TreeItem('Child 2', TraeAPI.TreeItemCollapsibleState.None)
]);
}
}
refresh(): void {
this._onDidChangeTreeData.fire();
}
}
class TreeItem extends TraeAPI.TreeItem {
constructor(
public readonly label: string,
public readonly collapsibleState: TraeAPI.TreeItemCollapsibleState
) {
super(label, collapsibleState);
this.tooltip = `Tooltip for ${label}`;
this.description = 'Description';
}
}
// Register tree view
const treeDataProvider = new MyTreeDataProvider();
const treeView = TraeAPI.window.createTreeView('myExtension.treeView', {
treeDataProvider,
showCollapseAll: true
});
context.subscriptions.push(treeView);Webview Panels
typescript
// Create webview panel
function createWebviewPanel() {
const panel = TraeAPI.window.createWebviewPanel(
'myExtension.webview',
'My Extension Panel',
TraeAPI.ViewColumn.One,
{
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots: [TraeAPI.Uri.joinPath(context.extensionUri, 'media')]
}
);
// Set HTML content
panel.webview.html = getWebviewContent(panel.webview, context.extensionUri);
// Handle messages from webview
panel.webview.onDidReceiveMessage(
message => {
switch (message.command) {
case 'alert':
TraeAPI.window.showErrorMessage(message.text);
return;
case 'getData':
panel.webview.postMessage({
command: 'dataResponse',
data: { items: ['item1', 'item2', 'item3'] }
});
return;
}
},
undefined,
context.subscriptions
);
return panel;
}
function getWebviewContent(webview: TraeAPI.Webview, extensionUri: TraeAPI.Uri): string {
const scriptUri = webview.asWebviewUri(
TraeAPI.Uri.joinPath(extensionUri, 'media', 'main.js')
);
const styleUri = webview.asWebviewUri(
TraeAPI.Uri.joinPath(extensionUri, 'media', 'main.css')
);
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="${styleUri}" rel="stylesheet">
<title>My Extension</title>
</head>
<body>
<h1>Hello from Webview!</h1>
<button id="alertBtn">Show Alert</button>
<button id="getDataBtn">Get Data</button>
<div id="dataContainer"></div>
<script src="${scriptUri}"></script>
</body>
</html>`;
}Configuration
Contributing Settings
typescript
// package.json configuration contribution
{
"contributes": {
"configuration": {
"title": "My Extension",
"properties": {
"myExtension.enable": {
"type": "boolean",
"default": true,
"description": "Enable My Extension"
},
"myExtension.maxItems": {
"type": "number",
"default": 10,
"minimum": 1,
"maximum": 100,
"description": "Maximum number of items"
},
"myExtension.customPath": {
"type": "string",
"default": "",
"description": "Custom path for extension"
}
}
}
}
}Reading Configuration
typescript
// Get configuration
const config = TraeAPI.workspace.getConfiguration('myExtension');
const isEnabled = config.get<boolean>('enable', true);
const maxItems = config.get<number>('maxItems', 10);
const customPath = config.get<string>('customPath', '');
// Listen to configuration changes
TraeAPI.workspace.onDidChangeConfiguration(event => {
if (event.affectsConfiguration('myExtension')) {
console.log('My extension configuration changed');
if (event.affectsConfiguration('myExtension.enable')) {
const newValue = TraeAPI.workspace.getConfiguration('myExtension').get('enable');
console.log('Enable setting changed to:', newValue);
}
}
});Updating Configuration
typescript
// Update configuration
const config = TraeAPI.workspace.getConfiguration('myExtension');
// Update global setting
await config.update('maxItems', 20, TraeAPI.ConfigurationTarget.Global);
// Update workspace setting
await config.update('enable', false, TraeAPI.ConfigurationTarget.Workspace);
// Update workspace folder setting
const workspaceFolder = TraeAPI.workspace.workspaceFolders?.[0];
if (workspaceFolder) {
await config.update('customPath', '/custom/path', TraeAPI.ConfigurationTarget.WorkspaceFolder);
}State Management
Workspace State
typescript
// Store workspace-specific data
const workspaceState = context.workspaceState;
// Store data
await workspaceState.update('lastOpenedFile', '/path/to/file.ts');
await workspaceState.update('userPreferences', { theme: 'dark', fontSize: 14 });
// Retrieve data
const lastFile = workspaceState.get<string>('lastOpenedFile');
const preferences = workspaceState.get<any>('userPreferences', { theme: 'light', fontSize: 12 });
// Get all keys
const keys = workspaceState.keys();
console.log('Stored keys:', keys);Global State
typescript
// Store global data (across all workspaces)
const globalState = context.globalState;
// Store data
await globalState.update('extensionVersion', '1.0.0');
await globalState.update('globalSettings', { autoUpdate: true });
// Retrieve data
const version = globalState.get<string>('extensionVersion');
const settings = globalState.get<any>('globalSettings', {});
// Sync data across devices
globalState.setKeysForSync(['globalSettings', 'userProfile']);Secret Storage
typescript
// Store sensitive data
const secrets = context.secrets;
// Store secret
await secrets.store('apiKey', 'your-secret-api-key');
await secrets.store('token', 'user-auth-token');
// Retrieve secret
const apiKey = await secrets.get('apiKey');
const token = await secrets.get('token');
// Delete secret
await secrets.delete('oldToken');
// Listen to secret changes
secrets.onDidChange(event => {
console.log('Secret changed:', event.key);
});Inter-Extension Communication
Extension API
typescript
// Extension A - Export API
export function activate(context: TraeAPI.ExtensionContext) {
// Return public API
return {
getVersion(): string {
return '1.0.0';
},
processData(data: any): Promise<any> {
return new Promise(resolve => {
// Process data
resolve({ processed: true, data });
});
},
onDataChanged: new TraeAPI.EventEmitter<any>().event
};
}
// Extension B - Use Extension A's API
export function activate(context: TraeAPI.ExtensionContext) {
const extensionA = TraeAPI.extensions.getExtension('publisher.extensionA');
if (extensionA) {
extensionA.activate().then(api => {
console.log('Extension A version:', api.getVersion());
// Use API
api.processData({ test: 'data' }).then(result => {
console.log('Processed result:', result);
});
// Listen to events
api.onDataChanged(data => {
console.log('Data changed:', data);
});
});
}
}Extension Dependencies
typescript
// package.json - Declare dependencies
{
"extensionDependencies": [
"publisher.requiredExtension"
],
"extensionPack": [
"publisher.relatedExtension1",
"publisher.relatedExtension2"
]
}
// Check if extension is installed and active
function checkExtensionDependency(extensionId: string): boolean {
const extension = TraeAPI.extensions.getExtension(extensionId);
return extension !== undefined && extension.isActive;
}
// Wait for extension to activate
async function waitForExtension(extensionId: string): Promise<any> {
const extension = TraeAPI.extensions.getExtension(extensionId);
if (!extension) {
throw new Error(`Extension ${extensionId} not found`);
}
if (!extension.isActive) {
await extension.activate();
}
return extension.exports;
}File System Access
Reading Files
typescript
// Read file content
const fileUri = TraeAPI.Uri.file('/path/to/file.txt');
const fileContent = await TraeAPI.workspace.fs.readFile(fileUri);
const textContent = Buffer.from(fileContent).toString('utf8');
// Read directory
const dirUri = TraeAPI.Uri.file('/path/to/directory');
const entries = await TraeAPI.workspace.fs.readDirectory(dirUri);
entries.forEach(([name, type]) => {
console.log(`${name} (${type === TraeAPI.FileType.Directory ? 'dir' : 'file'})`);
});
// Check if file exists
try {
const stat = await TraeAPI.workspace.fs.stat(fileUri);
console.log('File exists, size:', stat.size);
} catch (error) {
console.log('File does not exist');
}Writing Files
typescript
// Write file
const content = Buffer.from('Hello, World!', 'utf8');
await TraeAPI.workspace.fs.writeFile(fileUri, content);
// Create directory
const newDirUri = TraeAPI.Uri.file('/path/to/new/directory');
await TraeAPI.workspace.fs.createDirectory(newDirUri);
// Copy file
const sourceUri = TraeAPI.Uri.file('/path/to/source.txt');
const targetUri = TraeAPI.Uri.file('/path/to/target.txt');
await TraeAPI.workspace.fs.copy(sourceUri, targetUri);
// Delete file
await TraeAPI.workspace.fs.delete(fileUri);File Watchers
typescript
// Watch for file changes
const watcher = TraeAPI.workspace.createFileSystemWatcher('**/*.ts');
watcher.onDidCreate(uri => {
console.log('File created:', uri.fsPath);
});
watcher.onDidChange(uri => {
console.log('File changed:', uri.fsPath);
});
watcher.onDidDelete(uri => {
console.log('File deleted:', uri.fsPath);
});
context.subscriptions.push(watcher);Advanced Features
Custom Language Support
typescript
// Register language
const disposable = TraeAPI.languages.setLanguageConfiguration('mylang', {
comments: {
lineComment: '//',
blockComment: ['/*', '*/']
},
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
autoClosingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: "'", close: "'" }
],
surroundingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: "'", close: "'" }
]
});
context.subscriptions.push(disposable);Custom Themes
typescript
// package.json theme contribution
{
"contributes": {
"themes": [
{
"label": "My Dark Theme",
"uiTheme": "vs-dark",
"path": "./themes/dark-theme.json"
}
]
}
}
// Theme file structure (dark-theme.json)
{
"name": "My Dark Theme",
"type": "dark",
"colors": {
"editor.background": "#1e1e1e",
"editor.foreground": "#d4d4d4",
"activityBar.background": "#2d2d30",
"sideBar.background": "#252526"
},
"tokenColors": [
{
"scope": "comment",
"settings": {
"foreground": "#6A9955",
"fontStyle": "italic"
}
}
]
}Custom Icons
typescript
// package.json icon theme contribution
{
"contributes": {
"iconThemes": [
{
"id": "myIconTheme",
"label": "My Icon Theme",
"path": "./icons/icon-theme.json"
}
]
}
}
// Icon theme file
{
"iconDefinitions": {
"file": {
"iconPath": "./icons/file.svg"
},
"folder": {
"iconPath": "./icons/folder.svg"
}
},
"file": "file",
"folder": "folder",
"fileExtensions": {
"js": "javascript",
"ts": "typescript"
}
}Testing Extensions
Unit Tests
typescript
// test/suite/extension.test.ts
import * as assert from 'assert';
import * as TraeAPI from '@trae/api';
import * as myExtension from '../../src/extension';
suite('Extension Test Suite', () => {
TraeAPI.window.showInformationMessage('Start all tests.');
test('Extension should be present', () => {
assert.ok(TraeAPI.extensions.getExtension('publisher.my-extension'));
});
test('Should register commands', async () => {
const commands = await TraeAPI.commands.getCommands();
assert.ok(commands.includes('myExtension.helloWorld'));
});
test('Command should execute', async () => {
await TraeAPI.commands.executeCommand('myExtension.helloWorld');
// Assert expected behavior
});
});Integration Tests
typescript
// test/suite/integration.test.ts
import * as assert from 'assert';
import * as TraeAPI from '@trae/api';
import * as path from 'path';
suite('Integration Test Suite', () => {
let document: TraeAPI.TextDocument;
let editor: TraeAPI.TextEditor;
suiteSetup(async () => {
const uri = TraeAPI.Uri.file(path.join(__dirname, '../../test-fixtures/test.ts'));
document = await TraeAPI.workspace.openTextDocument(uri);
editor = await TraeAPI.window.showTextDocument(document);
});
test('Should format document', async () => {
await TraeAPI.commands.executeCommand('editor.action.formatDocument');
// Assert formatting changes
});
test('Should provide completions', async () => {
const position = new TraeAPI.Position(0, 0);
const completions = await TraeAPI.commands.executeCommand(
'vscode.executeCompletionItemProvider',
document.uri,
position
);
assert.ok(completions);
});
});Publishing Extensions
Package Configuration
json
{
"name": "my-extension",
"displayName": "My Extension",
"description": "A sample extension",
"version": "1.0.0",
"publisher": "my-publisher",
"engines": {
"trae": "^1.0.0"
},
"categories": [
"Other"
],
"keywords": [
"sample",
"extension"
],
"main": "./out/extension.js",
"scripts": {
"compile": "tsc -p ./",
"package": "trae-cli package",
"publish": "trae-cli publish"
}
}Build and Package
bash
# Install dependencies
npm install
# Compile TypeScript
npm run compile
# Package extension
npm run package
# Publish to marketplace
npm run publishBest Practices
Performance
- Lazy load heavy dependencies
- Use activation events appropriately
- Dispose of resources properly
- Avoid blocking the main thread
User Experience
- Provide clear error messages
- Use progress indicators for long operations
- Follow UI guidelines and conventions
- Test on different platforms
Security
- Validate user inputs
- Use secure storage for sensitive data
- Follow principle of least privilege
- Sanitize data before display
Examples
Complete Extension Example
typescript
// src/extension.ts
import * as TraeAPI from '@trae/api';
export function activate(context: TraeAPI.ExtensionContext) {
console.log('My extension is now active!');
// Register commands
const commands = [
TraeAPI.commands.registerCommand('myExtension.helloWorld', () => {
TraeAPI.window.showInformationMessage('Hello World!');
}),
TraeAPI.commands.registerCommand('myExtension.showInfo', () => {
const editor = TraeAPI.window.activeTextEditor;
if (editor) {
const document = editor.document;
const selection = editor.selection;
const text = document.getText(selection);
TraeAPI.window.showInformationMessage(
`Selected text: "${text}" in ${document.fileName}`
);
}
})
];
// Create status bar item
const statusBarItem = TraeAPI.window.createStatusBarItem(
TraeAPI.StatusBarAlignment.Right,
100
);
statusBarItem.text = '$(heart) My Extension';
statusBarItem.command = 'myExtension.helloWorld';
statusBarItem.show();
// Add to subscriptions
context.subscriptions.push(...commands, statusBarItem);
// Return API
return {
getExtensionInfo() {
return {
name: 'My Extension',
version: '1.0.0'
};
}
};
}
export function deactivate() {
console.log('My extension is now deactivated!');
}