👨‍💻

Copilotを使ってVisual Code の拡張を作ってみた

に公開

はじめに

Copilotを使って、Visual Code の拡張を作ってみました。
最初は、Windowsで作っていましたが、何やら以下のようなエラーが表示されていました。

Failed to load message bundle for file c:\Users\ユーザー名\.vscode\extensions\ms-edgedevtools.vscode-edge-devtools-2.1.6\out\extension

フォルダはありますが、extension というファイルがありません。extension.jsというファイルはあるのですが、stackoverflowなどのサイトを検索してみたのですが、ドライブレターが小文字であることが原因とか、どうも腑に落ちなかったので、結局諦めました。

でも、どうしても作ってみたかったので、Ubuntuで再挑戦。
紆余曲折の後に、なんとか作成することができました!
以下に手順を記録しておきます。

vs code のバージョンは、1.101.0, Ubuntu 24.04 での結果です。

準備

Copilotの指示に従って開発環境を構築します。

$ npm install -g yo generator-code

続いて、プロジェクトを作成します。

$ yo code

対話形式で色々答えていきます。

? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? 拡張名
? What's the identifier of your extension? 拡張名
? What's the description of your extension? 拡張の簡単な説明
? Initialize a git repository? GITを使うかどうか
? Which bundler to use? バンドルの方法
? Which package manager to use? パッケージマネージャは何を使う?

コードの準備

色々なサイトの情報を参考にコードを作りましょう。
因みに私は、.txtというファイルの拡張子を開いた時に、「重要」という言葉に色をつけるような拡張を作ってみました。
extension.ts

import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {
	console.log('Congratulations, your extension "myext" is now active!');

    const decorationType = vscode.window.createTextEditorDecorationType({
        backgroundColor: 'rgba(255, 165, 0, 0.5)', // ハイライト色
    });

    let activeEditor = vscode.window.activeTextEditor;

	function updateDecorations() {
        if (!activeEditor) return;
        
        const text = activeEditor.document.getText();
        const regEx = /重要/gi; // ハイライト対象の文言
        const matches: vscode.Range[] = [];
        let match;

        while ((match = regEx.exec(text))) {
            const startPos = activeEditor.document.positionAt(match.index);
            const endPos = activeEditor.document.positionAt(match.index + match[0].length);
            const range = new vscode.Range(startPos, endPos);
            matches.push(range);
        }

        activeEditor.setDecorations(decorationType, matches);
    }

    if (activeEditor) {
        updateDecorations();
    }

    vscode.window.onDidChangeActiveTextEditor(editor => {
        activeEditor = editor;
        if (editor) {
            updateDecorations();
        }
    }, null, context.subscriptions);

    vscode.workspace.onDidChangeTextDocument(event => {
        if (activeEditor && event.document === activeEditor.document) {
            updateDecorations();
        }
    }, null, context.subscriptions);
}

// This method is called when your extension is deactivated
export function deactivate() {}

デバッグ

最初デバッグでプログラムが正常動作しませんでした。
理由は幾つか設定の追加が必要だったことです。
この設定追加はバージョンの違いなのかよくわかりません。

ファイル拡張子の設定

package.json中に contributesという項目を見つけ以下の情報を追加します。
これは、この拡張と特定の拡張子を紐付けるものと思います。

"contributes": {
    ...
    "languages": [
        {
            "id": "plaintext",
            "extensions": [".txt"]
        }
    ],
    ...
}

Copilotからは上記の設定に加えて、以下の処理を追加できる旨の提案もありました。

if (vscode.window.activeTextEditor) {
    const languageId = vscode.window.activeTextEditor.document.languageId;
    console.log("Active Language ID:", languageId);

    if (languageId === "plaintext") {
        // ハイライト処理を実行
    }
}

拡張機能のファイル構造の確認

ここが一番ハマったところです。
以下の設定をしないと、デバッグしてもそもそも拡張が組み込まれていないようで、ログも出力されないし、ブレークポイントを設定しても停止しません。
package.json ファイル中にactivationEventsという設定があると思いますが、ここが空になっていると思います。

"activationEvents": [],  <= ここ
"main": "./dist/extension.js",

ここを以下のように修正します。

"activationEvents": [
  "onStartupFinished"
],
"main": "./dist/extension.js",

この設定をCopilotに聞いてみると、以下のような回答がありました。

"activationEvents"とは?

activationEventsは、Visual Studio Code拡張機能がどのタイミングでアクティブ化されるか(つまり初期化されるか)を指定する設定です。拡張機能がアクティブ化されない場合、activate関数は実行されず、当然その中のコードやログも動作しません。

なぜ、この設定が必要か?の問いには

デフォルトでは拡張機能が「何らかのトリガー」無しにはアクティブ化されません。そのため、package.jsonに何も指定されていないと、読み込まれているけど何も起こらない状態になることがあります。
"onStartupFinished"を追加したことで、Visual Studio Codeが起動した後すぐにこの拡張機能がアクティブ化されるようになりました。具体的には、onStartupFinishedは、VS Codeの起動完了後に自動的に拡張をロードするトリガーとして機能します。

なぜ、この最初に設定がなかったの?の問いには

これは、拡張機能の初期雛形を生成した際に、デフォルトの設定が最小限のものしか含まれていなかった可能性があります。Yeomanのジェネレーター(yo code)では、通常何も指定しなければ"activationEvents": []に近い状態で始まることがあります。

でした・・・指定するところ、あったっけ?

また、activationEventsのよく使われる例を説明してくれました。

以下はよく使われる一般的なトリガーです
"onLanguage:<languageId>" 特定の言語ファイルが開かれた時にアクティブ化。
"activationEvents": ["onLanguage:plaintext"]
"onCommand:<commandId>" ユーザーが特定のコマンドを実行したときにアクティブ化。
"activationEvents": ["onCommand:extension.helloWorld"]
"onFileSystem:<schema>" 特定のファイルシステムリソース(例: file や https)がアクセスされた場合。
"activationEvents": ["onFileSystem:file"]

デバッグ

F5を押してデバッグモードに入ります。
Run Extension にある隣の緑の三角ボタンを押してデバッグを開始します。
以下のような結果となりました。

終わりに

幾つか設定が必要な事が分からず手間がかかりましたが、Copilotを使ってなんとか解決することができました。

この後は、パッケージと公開になりますが、割愛します。
Copilotからは

完成した拡張機能をパッケージ化するにはvsceツールを使用します。マーケットプレイスに公開したい場合にも役立ちます。

npm install -g vsce
vsce package

が提案されていました。
その他、Qiitaの記事なども紹介されていました。

Discussion