Skip to content

拡張機能開発

概要

Trae IDEの拡張機能開発により、IDEの機能を拡張し、カスタマイズできます。このガイドでは、拡張機能の作成、テスト、公開について説明します。

拡張機能の種類

テーマ拡張機能

json
{
  "name": "my-custom-theme",
  "type": "theme",
  "version": "1.0.0",
  "description": "カスタムテーマ拡張機能",
  "contributes": {
    "themes": [
      {
        "label": "My Dark Theme",
        "uiTheme": "vs-dark",
        "path": "./themes/dark-theme.json"
      }
    ]
  }
}

言語サポート拡張機能

json
{
  "name": "my-language-support",
  "type": "language",
  "version": "1.0.0",
  "description": "新しい言語のサポート",
  "contributes": {
    "languages": [
      {
        "id": "mylang",
        "aliases": ["MyLang", "mylang"],
        "extensions": [".ml"],
        "configuration": "./language-configuration.json"
      }
    ],
    "grammars": [
      {
        "language": "mylang",
        "scopeName": "source.mylang",
        "path": "./syntaxes/mylang.tmGrammar.json"
      }
    ]
  }
}

機能拡張機能

json
{
  "name": "my-feature-extension",
  "type": "feature",
  "version": "1.0.0",
  "description": "新機能を追加する拡張機能",
  "contributes": {
    "commands": [
      {
        "command": "myextension.helloWorld",
        "title": "Hello World",
        "category": "My Extension"
      }
    ],
    "menus": {
      "commandPalette": [
        {
          "command": "myextension.helloWorld",
          "when": "editorTextFocus"
        }
      ]
    }
  }
}

開発環境のセットアップ

プロジェクト初期化

bash
# 拡張機能プロジェクトを作成
mkdir my-extension
cd my-extension

# package.jsonを初期化
npm init -y

# 必要な依存関係をインストール
npm install --save-dev @types/trae-ide
npm install --save-dev typescript
npm install --save-dev webpack webpack-cli

プロジェクト構造

my-extension/
├── package.json          # 拡張機能のマニフェスト
├── tsconfig.json         # TypeScript設定
├── webpack.config.js     # ビルド設定
├── src/
│   ├── extension.ts      # メインエントリーポイント
│   ├── commands/         # コマンド実装
│   ├── providers/        # プロバイダー実装
│   └── utils/           # ユーティリティ
├── resources/           # リソースファイル
├── themes/             # テーマファイル
└── syntaxes/           # 構文ハイライト定義

TypeScript設定

json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "out",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "out"]
}

拡張機能API

基本的な拡張機能

typescript
// src/extension.ts
import * as trae from 'trae-ide';

export function activate(context: trae.ExtensionContext) {
    console.log('拡張機能が有効化されました');

    // コマンドを登録
    const disposable = trae.commands.registerCommand('myextension.helloWorld', () => {
        trae.window.showInformationMessage('Hello World from My Extension!');
    });

    context.subscriptions.push(disposable);
}

export function deactivate() {
    console.log('拡張機能が無効化されました');
}

コマンドの実装

typescript
// src/commands/myCommands.ts
import * as trae from 'trae-ide';

export class MyCommands {
    static registerCommands(context: trae.ExtensionContext) {
        // ファイル作成コマンド
        const createFileCommand = trae.commands.registerCommand(
            'myextension.createFile',
            this.createFile
        );

        // コード整形コマンド
        const formatCodeCommand = trae.commands.registerCommand(
            'myextension.formatCode',
            this.formatCode
        );

        context.subscriptions.push(createFileCommand, formatCodeCommand);
    }

    private static async createFile() {
        const fileName = await trae.window.showInputBox({
            prompt: 'ファイル名を入力してください',
            placeHolder: 'example.txt'
        });

        if (fileName) {
            const workspaceFolder = trae.workspace.workspaceFolders?.[0];
            if (workspaceFolder) {
                const filePath = trae.Uri.joinPath(workspaceFolder.uri, fileName);
                await trae.workspace.fs.writeFile(filePath, Buffer.from(''));
                trae.window.showInformationMessage(`ファイル ${fileName} を作成しました`);
            }
        }
    }

    private static async formatCode() {
        const editor = trae.window.activeTextEditor;
        if (editor) {
            const document = editor.document;
            const text = document.getText();
            
            // カスタム整形ロジック
            const formattedText = this.customFormat(text);
            
            const edit = new trae.WorkspaceEdit();
            edit.replace(
                document.uri,
                new trae.Range(0, 0, document.lineCount, 0),
                formattedText
            );
            
            await trae.workspace.applyEdit(edit);
        }
    }

    private static customFormat(text: string): string {
        // カスタム整形ロジックを実装
        return text.split('\n').map(line => line.trim()).join('\n');
    }
}

プロバイダーの実装

typescript
// src/providers/completionProvider.ts
import * as trae from 'trae-ide';

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 completionItems: trae.CompletionItem[] = [];

        // カスタム補完アイテムを作成
        const item1 = new trae.CompletionItem('myFunction', trae.CompletionItemKind.Function);
        item1.detail = 'カスタム関数';
        item1.documentation = new trae.MarkdownString('カスタム関数の説明');
        item1.insertText = new trae.SnippetString('myFunction(${1:param})');

        const item2 = new trae.CompletionItem('myVariable', trae.CompletionItemKind.Variable);
        item2.detail = 'カスタム変数';
        item2.insertText = 'myVariable';

        completionItems.push(item1, item2);

        return completionItems;
    }
}

// プロバイダーを登録
export function registerCompletionProvider(context: trae.ExtensionContext) {
    const provider = new MyCompletionProvider();
    const disposable = trae.languages.registerCompletionItemProvider(
        { scheme: 'file', language: 'javascript' },
        provider,
        '.' // トリガー文字
    );
    
    context.subscriptions.push(disposable);
}

ホバープロバイダー

typescript
// src/providers/hoverProvider.ts
import * as trae from 'trae-ide';

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 === 'myFunction') {
            const contents = new trae.MarkdownString();
            contents.appendCodeblock('function myFunction(param: string): void', 'typescript');
            contents.appendMarkdown('カスタム関数の詳細説明');
            
            return new trae.Hover(contents, range);
        }

        return null;
    }
}

設定とカスタマイゼーション

拡張機能設定

json
{
  "contributes": {
    "configuration": {
      "title": "My Extension",
      "properties": {
        "myextension.enableFeature": {
          "type": "boolean",
          "default": true,
          "description": "機能を有効にする"
        },
        "myextension.customPath": {
          "type": "string",
          "default": "",
          "description": "カスタムパス"
        },
        "myextension.logLevel": {
          "type": "string",
          "enum": ["debug", "info", "warn", "error"],
          "default": "info",
          "description": "ログレベル"
        }
      }
    }
  }
}

設定の読み取り

typescript
// src/utils/config.ts
import * as trae from 'trae-ide';

export class ConfigManager {
    private static readonly EXTENSION_NAME = 'myextension';

    static get<T>(key: string, defaultValue?: T): T {
        const config = trae.workspace.getConfiguration(this.EXTENSION_NAME);
        return config.get<T>(key, defaultValue as T);
    }

    static async set(key: string, value: any, target?: trae.ConfigurationTarget) {
        const config = trae.workspace.getConfiguration(this.EXTENSION_NAME);
        await config.update(key, value, target);
    }

    static isFeatureEnabled(): boolean {
        return this.get<boolean>('enableFeature', true);
    }

    static getCustomPath(): string {
        return this.get<string>('customPath', '');
    }

    static getLogLevel(): string {
        return this.get<string>('logLevel', 'info');
    }
}

テストとデバッグ

ユニットテスト

typescript
// src/test/extension.test.ts
import * as assert from 'assert';
import * as trae from 'trae-ide';
import { MyCommands } from '../commands/myCommands';

suite('Extension Test Suite', () => {
    trae.window.showInformationMessage('テストを開始します');

    test('コマンド登録テスト', () => {
        // コマンドが正しく登録されているかテスト
        const commands = trae.commands.getCommands();
        assert.ok(commands.includes('myextension.helloWorld'));
    });

    test('設定読み取りテスト', () => {
        // 設定が正しく読み取れるかテスト
        const config = trae.workspace.getConfiguration('myextension');
        assert.strictEqual(typeof config.get('enableFeature'), 'boolean');
    });
});

デバッグ設定

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "拡張機能をデバッグ",
      "type": "extensionHost",
      "request": "launch",
      "runtimeExecutable": "${execPath}",
      "args": [
        "--extensionDevelopmentPath=${workspaceFolder}"
      ],
      "outFiles": [
        "${workspaceFolder}/out/**/*.js"
      ],
      "preLaunchTask": "npm: compile"
    }
  ]
}

ビルドとパッケージング

Webpack設定

javascript
// 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-ide': 'commonjs trae-ide'
    },
    resolve: {
        extensions: ['.ts', '.js']
    },
    module: {
        rules: [
            {
                test: /\.ts$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'ts-loader'
                    }
                ]
            }
        ]
    }
};

ビルドスクリプト

json
{
  "scripts": {
    "compile": "webpack --mode development",
    "compile-production": "webpack --mode production",
    "watch": "webpack --mode development --watch",
    "test": "node ./out/test/runTest.js",
    "package": "vsce package",
    "publish": "vsce publish"
  }
}

公開とディストリビューション

拡張機能のパッケージング

bash
# 拡張機能をパッケージング
npm run package

# 生成されたVSIXファイルをインストール
code --install-extension my-extension-1.0.0.vsix

マーケットプレイスへの公開

bash
# 公開用トークンを設定
vsce login <publisher-name>

# 拡張機能を公開
npm run publish

継続的インテグレーション

yaml
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Compile
        run: npm run compile
      
      - name: Run tests
        run: npm test
      
      - name: Package extension
        run: npm run package
      
      - name: Upload artifact
        uses: actions/upload-artifact@v3
        with:
          name: extension-package
          path: '*.vsix'

ベストプラクティス

パフォーマンス最適化

typescript
// 遅延読み込み
export async function activate(context: trae.ExtensionContext) {
    // 重い処理は必要時まで遅延
    context.subscriptions.push(
        trae.commands.registerCommand('myextension.heavyOperation', async () => {
            const { performHeavyOperation } = await import('./heavyModule');
            await performHeavyOperation();
        })
    );
}

// キャッシュの活用
class CacheManager {
    private static cache = new Map<string, any>();
    
    static get<T>(key: string): T | undefined {
        return this.cache.get(key);
    }
    
    static set<T>(key: string, value: T): void {
        this.cache.set(key, value);
    }
}

エラーハンドリング

typescript
// src/utils/errorHandler.ts
import * as trae from 'trae-ide';

export class ErrorHandler {
    static handleError(error: Error, context?: string) {
        console.error(`[My Extension] ${context || 'Error'}:`, error);
        
        trae.window.showErrorMessage(
            `エラーが発生しました: ${error.message}`
        );
    }

    static async safeExecute<T>(
        operation: () => Promise<T>,
        context: string
    ): Promise<T | undefined> {
        try {
            return await operation();
        } catch (error) {
            this.handleError(error as Error, context);
            return undefined;
        }
    }
}

国際化対応

typescript
// src/utils/i18n.ts
import * as trae from 'trae-ide';

export class I18n {
    private static messages: { [key: string]: string } = {
        'hello.world': 'Hello World',
        'file.created': 'ファイルが作成されました',
        'error.occurred': 'エラーが発生しました'
    };

    static getMessage(key: string, ...args: string[]): string {
        let message = this.messages[key] || key;
        
        args.forEach((arg, index) => {
            message = message.replace(`{${index}}`, arg);
        });
        
        return message;
    }
}

トラブルシューティング

よくある問題

Q: 拡張機能が読み込まれない A: package.jsonactivationEventsを確認し、適切なイベントが設定されているか確認してください。

Q: コマンドが表示されない A: contributes.commandsでコマンドが正しく定義され、registerCommandで登録されているか確認してください。

Q: TypeScriptコンパイルエラー A: @types/trae-ideが正しくインストールされ、tsconfig.jsonの設定が適切か確認してください。

デバッグのヒント

typescript
// デバッグ用ログ出力
const outputChannel = trae.window.createOutputChannel('My Extension');

function debugLog(message: string) {
    outputChannel.appendLine(`[${new Date().toISOString()}] ${message}`);
}

// 拡張機能の状態確認
function checkExtensionState() {
    debugLog('拡張機能の状態をチェック中...');
    debugLog(`アクティブエディタ: ${trae.window.activeTextEditor?.document.fileName || 'なし'}`);
    debugLog(`ワークスペース: ${trae.workspace.workspaceFolders?.length || 0} フォルダ`);
}

高度な機能

カスタムビューの作成

typescript
// src/views/customView.ts
import * as trae from 'trae-ide';

export class CustomViewProvider implements trae.TreeDataProvider<CustomItem> {
    private _onDidChangeTreeData: trae.EventEmitter<CustomItem | undefined | null | void> = new trae.EventEmitter<CustomItem | undefined | null | void>();
    readonly onDidChangeTreeData: trae.Event<CustomItem | undefined | null | void> = this._onDidChangeTreeData.event;

    constructor() {}

    refresh(): void {
        this._onDidChangeTreeData.fire();
    }

    getTreeItem(element: CustomItem): trae.TreeItem {
        return element;
    }

    getChildren(element?: CustomItem): Thenable<CustomItem[]> {
        if (!element) {
            return Promise.resolve([
                new CustomItem('アイテム1', trae.TreeItemCollapsibleState.None),
                new CustomItem('アイテム2', trae.TreeItemCollapsibleState.None)
            ]);
        }
        return Promise.resolve([]);
    }
}

class CustomItem extends trae.TreeItem {
    constructor(
        public readonly label: string,
        public readonly collapsibleState: trae.TreeItemCollapsibleState
    ) {
        super(label, collapsibleState);
        this.tooltip = `${this.label} - カスタムアイテム`;
    }
}

このガイドを参考に、Trae IDE用の強力で使いやすい拡張機能を開発してください。

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