2022/12/23

テクノロジー

VSCode の拡張機能を作ってみる

この記事の目次

    はじめに

    唐突ですが VSCode の拡張機能作ってみたくありませんか?
    私は日々のコーディングの業務ではもちろん VSCode を使っていますが、痒いところに手が届かないといいますか、自分の思い通りのコードエディタにするために、あと一歩足りない・・・この機能があれば・・・と思うことがあったりします。
    みなさんもこう感じた経験ないですか?

    こういったニッチな要望に答えてくれる拡張機能があればいいですが、なければ自分で作るしかありません。
    ということで今回は VSCode の拡張機能の作り方をこの記事で紹介したいと思います!

    とりあえず Hello World してみる

    デバッグで動かしてみる

    VSCode の公式ドキュメント の Get Started を見てみるとサンプルの拡張機能(拡張機能の雛形)を作る方法が載っています。

    この手順に従ってサンプルの拡張機能を作ってみます。
    以下のコマンドを打って必要なモジュールをインストールして雛形を作ってみます。

    npm install -g yo generator-code
    yo code

    雛形は yo コマンドが対話的に作成してくれます。
    今回は TypeScript を使って雛形をつくることとして、名前は適当にHelloWorldとします。

    # ? What type of extension do you want to create?
    > New Extension (TypeScript)
    # ? What's the name of your extension? HelloWorld
    # ? What's the identifier of your extension? hellowold # そのまま Enter
    # ? What's the description of your extension? Sample extension
    # ? Initialize a git repository? n # リポジトリとしたい場合は Y を選択
    # ? Bundle the source code with webpack? N
    # ? Which package manager to use? npm # お好みのものを選んでください
    
    # ? Do you want to open the new folder with Visual Studio Code?
    > Open with `code`

    これで、HelloWorld 拡張機能の雛形が作られました。
    このままF5を押して拡張機能をデバッグモードで起動させてみます。
    HelloWorld 拡張機能が有効になったデバッグ用の VSCode のウインドウが開きます。
    この状態で、Ctrl + Shift + pを押して、コマンドパレットを表示させ、Hello Worldと入力するとコマンドがでてきます。

    コマンドを実行してみると、右下のテキストボックスにHello World from HelloWorldが表示されました。

    コードを見てみる

    拡張機能のエントリポイントは、src/extension.ts 内のactivate関数になります。
    中身は👇です。(実際のコードではコメントが丁寧に入っています。)

    ▼helloworld/src/extension.ts

    import * as vscode from 'vscode';
    
    export function activate(context: vscode.ExtensionContext) {
    
        console.log('Congratulations, your extension "helloworld" is now active!');
    
        let disposable = vscode.commands.registerCommand('helloworld.helloWorld', () => {
            vscode.window.showInformationMessage('Hello World from HelloWorld!');
        });
    
        context.subscriptions.push(disposable);
    }
    
    export function deactivate() {}

    コードの中盤あたり👇のdisposableオブジェクトが Hello World を表示させているコマンドの実態です。

    let disposable = vscode.commands.registerCommand('helloworld.helloWorld', () => {
            vscode.window.showInformationMessage('Hello World from HelloWorld!');
        });

    ここでは、vscode.window.showInformationMessage関数を呼び出すコマンドを作成して、そのコマンドの ID をhelloworld.helloWorldとしています。(showInformationMessageはVSCode の右下部分にメッセージを表示させるメソッドです)

    ここで、サンプルの拡張機能で Hello World を表示させたときのことを思い出してみます。
    右下に Hello World が出るのはコマンドパレットに Hello World を入力し、コマンドを実行したときでした。
    このようにコマンドパレットから実行可能なコマンドにしている部分の記述が実は package.jsonに記載されています。

    package.jsonの16-23行目に該当の記述があります。👇

    ▼helloworld/package.json(一部抜粋)

      "contributes": {
        "commands": [
          {
            "command": "helloworld.helloWorld",
            "title": "Hello World"
          }
        ]
      },

    この部分で、コマンドのタイトルをHello Worldとし、helloworld.helloWorldをこの拡張機能が提供するコマンドとして登録しています。

    このようにして、VSCode の拡張機能では package.json とソースコードに所望の処理を記述して、拡張機能を作っていくことになります。

    パッケージ化📦してみる

    サンプルの拡張機能の中身を理解できたところで、次はこれを実際に使える形でパッケージ化 📦してみます。
    パッケージ化するところについては👇の記事がよくまとまっているので参考にします。
    https://dev.classmethod.jp/articles/easy-vs-code-extension-development/
    ※ 今回の記事では拡張機能を公開する部分についてはやったことがないので触れません。

    パッケージ化するためには vsce コマンドを使うようです。
    とりあえずインストールしておきます。

    npm install -g vsce

    正しくパッケージ化するために、まずは README を編集します。
    ここを編集しないと vsce コマンドを実行したときに以下のエラーが発生してパッケージ化できません(2敗)

    ERROR  Make sure to edit the README.md file before you package or publish your extension.
    # helloworld README
    
    sample extension

    README を直したら vsce コマンドを実行して拡張機能をパッケージ化します。

    npx vsce package

    場合によっては警告がでるので、適切に対応します。

    WARNING  A 'repository' field is missing from the 'package.json' manifest file.
    Do you want to continue? [y/N] y # git リポジトリにしてない場合に発生します
    WARNING  LICENSE.md, LICENSE.txt or LICENSE not found
    Do you want to continue? [y/N] y

    完了すると、{拡張機能名}-{バージョン}.vsixファイルが出来上がりました。(ちなみにバージョン名や拡張機能名はpackage.jsonで管理されています。)
    これでパッケージ化は完了です🎉

    実際にインストールするときはCtrl + Shift + xで拡張機能のビューを表示させVSIX からのインストール を選択し VSCode にインストールします。

    おすすめのやり方について話してみる

    VSCode の API リファレンス を見ると様々な API の使い方が載っているわけですが、API の呼び出し方を説明するのみで、その機能を使うことで見た目がどう変わるのか、内部で何が起こっているのかをイメージすることは正直厳しいと思います。
    例えば、エクスプローラーのようなツリー型のビュー(👇みたいなやつ)を作りたい場合に、膨大な使い方から作り方を探すのは大変だと思います。

    そんなときは、VSCode の公式が公開している サンプル集 をみて、動くものから学んでいくのがおすすめです。

    ここでは、よく使いがちな機能のサンプルをまとめており、クローンしてきて実際に動かしてみることで中で何が起こっているのか、見た目にどう影響するのかを確認してみることができます。(vim エディタ:memo:を作ってみる ってのもあったりします )
    実際に動かしてみて、中身を確認したあと API リファレンス を見ることで理解がしやすくなるんじゃないかと思います。(実際私はそうやって拡張機能の開発をしてます)

    また、VSCode の UI 構造やロジック部分の記述に慣れてくると、dockerの拡張機能gitlens といった有名な拡張機能のソースコードから、所望の処理の部分を参考にして実装してみるのもいいと思います。

    サンプル拡張機能に機能を追加してみる

    最後に、紹介したおすすめのやり方でサンプルの拡張機能に👇の機能を追加してみます。

    • VSCode の下部ステータスバーに文字を表示させる
    • 文字情報は VSCode の設定画面から指定する

    ステータスバーに文字を出す部分について調べてみる

    ステータスバーに文字を追加している拡張機能をサンプルのリポジトリから探してみます。
    👇のコード内、StatusBarクラスの vscode.StatusBarItemが該当の部分っぽそうです。
    https://github.com/microsoft/vscode-extension-samples/blob/main/vim-sample/src/extension.ts

    ▼vim-sample/src/extension.ts

    class StatusBar {
        private _actual: vscode.StatusBarItem;
        private _lastText: string;
    
        constructor() {
            this._actual = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
            this._actual.show();
        }
    
        public setText(text: string): void {
            if (this._lastText === text) {
                return;
            }
            this._lastText = text;
            this._actual.text = this._lastText;
        }
    }

    文字を表示させるにはvscode.StatusBarItemオブジェクトを作る必要があり、それはvscode.window.createStatusBarItem関数で、できるようです。
    表示させる文字はどのように設定するのかも見てみます。
    同じクラスのsetTextメソッド内のコードを見ると、vscode.StatusBarItemオブジェクトの text プロパティに表示する文字を入れているみたいです。

    API リファレンスも見てみます。
    createStatusBarItem関数は このリンク をちょっとスクロールした部分に定義がありました。

    StatusBarItemクラスについてはこちらの リンク に使い方があるみたいです。
    text プロパティを見てみると、どうやら特殊な書き方でアイコンも表示させることができることもわかりました。

    設定画面から文字を取得する部分について調べてみる

    次に設定画面から文字を得る部分を調べてみます。
    このコード の👇あたりが設定から値を持ってきているところみたいです。

    ▼codelens-sample/src/CodelensProvider.ts

    if (vscode.workspace.getConfiguration("codelens-sample").get("enableCodeLens", true)) {

    getConfigration関数の リファレンス も見てみると、WorkspaceConfigurationクラスのインスタンスを返していることがわかり、その部分のリファレンス も見てみるとgetメソッドの使い方も分かってきました。

    次に configration とは何かについても調べてみます。
    リファレンスを探してみると、package.json に記述する設定みたいです。
    参考にした拡張機能の pakcage.json を見ると configration の記述があることも確認できます。

    ▼codelens-sample/package.json

    "configuration": {
      "properties": {
        "codelens-sample.enableCodeLens": {
          "type": "boolean",
          "default": true
        }
      }
    }

    実装してみる

    これで機能追加する上で必要な情報が出揃ったので、実際に機能追加してみます。😎

    今回はstatusBarTextという設定を追加してみて、ここの値をステータスバーに出すことにします。(デフォルトの値としてdefaultという文字を入れておきます。)
    また、文字の表示はウインドウが立ち上がったときにしたいので、activationEventsonStartupFinishedを追加しています。

    ▼helloworld/package.json(一部抜粋)

      "activationEvents": [
        "onCommand:helloworld.helloWorld",
        "onStartupFinished" // 追加部分(拡張機能が有効化されたときに active 関数を実行する)
      ],
      ...
      "contributes": {
        "commands": [
          {
            "command": "helloworld.helloWorld",
            "title": "Hello World"
          }
        ],
        // 追加部分
        "configuration": [
          {
            "type": "object",
            "title": "helloworld",
            "properties": {
              "helloworld.statusBarText": {
                "type": "string",
                "default": "dafault",
                "scope": "resource",
                "description": "ステータスバーに表示する文字を設定します"
              }
            }
          }
        ]
      }

    extension.ts にはサンプルのコードを見て学んだ API の使い方を参考に、以下を行うコードを追記します。

    • ステータスバーを作る
    • 文字を設定から読み込む
    • ステータスバーの文字として設定する

    ▼helloworld/src/extension.ts

    import * as vscode from 'vscode';
    
    export function activate(context: vscode.ExtensionContext) {
    
        console.log('Congratulations, your extension "helloworld" is now active!');
    
        let disposable = vscode.commands.registerCommand('helloworld.helloWorld', () => {
            vscode.window.showInformationMessage('Hello World from HelloWorld!');
        });
    
        context.subscriptions.push(disposable);
    
        // 追加部分
        const statusBarItem = vscode.window.createStatusBarItem();
        statusBarItem.text = vscode.workspace
            .getConfiguration('helloworld')
            .get('statusBarText', '');
    
        statusBarItem.show();
    }
    
    export function deactivate() {}

    それではデバッグモードで実行してみます。

    値が表示されています🎉
    起動したばっかりでは、設定画面で値を指定していないのでデフォルトの文字が表示されています。

    思った通りの動作をするか見てみる

    次に設定ファイルに値を記入して、表示する文字を変更してみます。
    Ctrl + ,を入力して設定画面を出して、検索ボックスにhelloworldと入力します。
    👇のように設定が出てくるので適当な文字を入れてみます。
    この際どうせならアイコンも表示させてみます。(アイコンの一覧

    文字が表示されるのは拡張機能が読み込まれたときなので、再読み込みさせます。
    デバッグコンソールを表示している方の VSCode ウインドウから更新ボタンを押して再度デバッグを実行してみます。

    下の文字が変更されました!
    アイコンもうまく表示できているみたいです!🎉

    おわりに

    今回は VSCode の拡張機能の作り方と、実際に作るときに参考にしたら良いドキュメントやサンプルコードを紹介しました。
    これだけ多くのことができて、自由度が高いところが VSCode が使いやすいソースコードエディタとして知られている理由なんだと思います。
    日々の業務で感じている僅かな苛立ちを、自分で拡張機能を作ってみて解決してみてはいかがでしょうか。

    ※本記事は2022年12月時点の情報です。

    著者:マイナビエンジニアブログ編集部