Skip to content

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 publish

Best 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!');
}

より多くの例と詳細なガイドについては、拡張機能開発ガイド拡張機能サンプルリポジトリを参照してください。

究極の AI 駆動 IDE 学習ガイド