VS Code + WSL2でVS Codeの拡張機能を作ってみる

VS Code + WSL2でVS Codeの拡張機能を作ってみるVisual Studio Code
スポンサーリンク

数回前にWordPressの更新に便利なVS Codeの拡張機能WordPress Postを紹介しました。

この拡張機能を試してみて、VS Codeでの拡張機能の作り方に興味がでてきたので、基本的な作り方を調べてみたいと思います。

スポンサーリンク

VS Codeの拡張機能

Visual Studio Code (VS Code)には非常に多くの拡張機能が公開されています。

Visual Studio Marketplace
Extensions for Visual Studio family of products on Visual Studio Marketplace

このブログでもいくつか拡張機能を紹介してきました。

プログラミング(コーディング)に便利なVS Codeの拡張機能
今回はプログラミング(コーディング)に便利はVisual Studio Code(VS Code)の拡張機能を紹介します。VS Codeには様々な拡張機能があり、導入する拡張機能により使い勝手が大きく変わります。ここで紹介した拡張機能以外にもいろいろあるので、自分にあった拡張機能を探すと良いでしょう。
Emacs使いがVS Codeを使う
今回はVS CodeでEmacsのキーバインドを実現するために、Awesome Emacs Keymapという拡張機能を導入したことを紹介します。この拡張機能を導入すると、カーソル移動や文字の編集などがEmacsと同じキー操作で行うことができます。Emacsになれている人がVS Codeを使う際には必ず入れて置きたい拡張機能と思います。

VS Codeの魅力の1つがこれらの拡張機能によって、使い勝手や機能を拡張できることだと思います。

その拡張機能ですが当然ながら自分で作成することもできます。オフィシャルなガイドは英語となりますが、下記になります。

Extension API
Visual Studio Code has a rich extension API. Learn how to create your own extensions for VS Code.

今回はこのオフィシャルドキュメントの「GET STARTED」を試してみたいと思います。

ちなみに利用する環境はWindows上のVS Codeですが、Remote-WSL拡張機能を導入して、WSL2 (ディストリビューションはUbuntu 20.04LTS)上で開発を行います。

Remote-WSL拡張機能については下記を参照してください。

WSL2とVS CodeでC言語を利用してみる (Remote - WSL編)
今回はWSL2でLinuxを導入したWindowsにVS Codeをインストールして、C言語を使って見ます。拡張機能「Remote – WSL」を使ってVS CodeとWSL2を連携させると、Linuxのコマンドラインを一切使わずに、LinuxでC言語のプログラムをコンパイル・デバッグすることができます。かなり開発の敷居が下がるのではないでしょうか。
スポンサーリンク

準備

まずVS Codeの拡張機能を開発するために必要な環境を整えます。

今回はRemote-WSL拡張機能を使ってWSL2(Ubuntu 20.04LTS)上で開発するので、環境の整備はWSL2上で行っていきます。

VS Codeの拡張を機能を開発するためにはまずNode.jsが必要となります。

私がWSL2で利用しているUbuntu 20.04LTSにもNode.jsのパッケージがあるのですが、こちらのバージョンは「v10.19.0」となっており、かなり古めです。そのため、後述のYeomanというツールが動きません。

ここはNodeSourceで提供されているパッケージを利用しましょう。利用するバージョンはこの記事の作成時点(2022年6月)にアクティブLTSであるv16です。

まずNodeSourceの提供するパッケージ情報を取り込みます。

$ curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -

つづいてNode.jsをインストールします。

$ sudo apt-get install nodejs

私が試したときにはインストールされたNode.jsのバージョンはV16.15.1でした。

$ node --version
v16.15.1

次にYeomanというツールとVS Code Extension Generatorを次のコマンドで導入します。

$ sudo npm install -g yo generator-code

これでとりあえず準備完了です。

私が試したときには次のようなメッセージが表示されました。

added 899 packages, and audited 900 packages in 25s

64 packages are looking for funding
  run `npm fund` for details

7 high severity vulnerabilities

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.
npm notice
npm notice New minor version of npm available! 8.11.0 -> 8.12.1
npm notice Changelog: https://github.com/npm/cli/releases/tag/v8.12.1
npm notice Run npm install -g npm@8.12.1 to update!
npm notice

脆弱性(vulnerabilities)は気になるところですが、とりあえず新しいパッケージがあるというものだけは更新しておきました。

$ sudo npm install -g npm@8.12.1

サンプルプロジェクトの準備

環境が用意できたら、適当なフォルダを作成して、シンプルな拡張機能のコードを生成します。

$ mkdir -p ~/projects/first-extension
$ cd ~/projects/first-extension
$ yo code

しばらくすると次のようなメニューが表示されます。

コードの生成 - タイプの選択

上下のカーソルキーでメニュー項目を選べるので「New Extension (TypeScript)」を選択します。選択はリターンキーです。

続いて拡張機能の名前を要求されます。

コードの生成 - 拡張機能の名前

今回は「HelloWorld」と入力します。

次は拡張機能の識別子の入力ですが、拡張機能の名前を元に自動設定されているので、そのままリターンキーを押せばOKです。

コードの生成 - 拡張機能の識別子

次は拡張機能の説明文です。これは空欄でも問題ないので、そのままリターンキーを押します。

コードの生成 - 拡張機能の説明

次はGitレポジトリして初期化するかどうかなので「Y」を入力します。

コードの生成 - gitレポジトリの初期化

その次はwebpackを利用するかどうかですが「N」として良いようです。

コードの生成 - webpackの利用

次は利用するパッケージマネージャの選択なので「npm」を選択します。

コードの生成 - パッケージマネージャの選択

これでファイルが生成され、また、必要なパッケージが導入されます。

私の場合は、途中でgit initのエラーが出ましたが、とりあえず無視します。

最後にVS Codeを起動するかどうかを聞かれます。

VS Codeの起動

WSL2上でWindowsのVS Codeを起動できる状態であれば、ここは「Open with `code`」を選んでOKです。

VS Codeが立ち上がると、フォルダを信用するかどうかを聞かれるので「はい、作成者を信頼します」を選択します。

フォルダの信用

また、オススメの拡張機能が表示されたらインストールしておきます。

拡張機能の追加

最終的にはVS Codeのエクスプローラは次のような状態になるはずです。

生成されたフォルダ

これで拡張機能の作成するためのプロジェクトが作成できました。

ついでにこのプロジェクトを実行してみましょう。VS Codeのメニューで「実行」→「デバッグの開始」を選択するかF5キーを押してみます。

デバッグの開始

すると新しいVS Codeのウィンドウが立ち上がります。このウィンドウの名称部分には「開発環境開発ホスト」と表示されており、開発環境を試すための特別なウィンドウであることがわかります。

デバッグ用のVS Codeウィンドウ

先ほどコード生成をしたHello World拡張機能は、この新しく生成されたウィンドウで有効になっています。

そこでこのウィンドウのコマンドパレットで「hello」と売ってみると、「Hello World」というコマンドが表示されます。

Hello Worldコマンドの実行

そしてこの「Hello World」を選択して実行してみると、画面の右下に「Hello World from HelloWorld!」と表示されます。

メッセージの表示

このメッセージはコード生成で作成された拡張機能により表示されたものになります。

ちなみに「開発環境開発ホスト」のウィンドウを閉じるか、元のVS Codeのウィンドウで表示されるデバッグ用にツールバーで停止を選択すると、拡張機能の実行(デバッグ)は終了します。

デバッグの終了

今回はVS CodeをWSL2と連携させていますが、問題なく生成した拡張機能をVS Code上から実行できることがわかります。

コードを理解する

自動生成されたコードが動作することを確認したので、これがどんなソースコードから実現されているのかを確認してみます。

この拡張機能のソースコードはsrcフォルダ内のextension.tsです。

内容は次のようになります。

// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
	
	// Use the console to output diagnostic information (console.log) and errors (console.error)
	// This line of code will only be executed once when your extension is activated
	console.log('Congratulations, your extension "helloworld" is now active!');

	// The command has been defined in the package.json file
	// Now provide the implementation of the command with registerCommand
	// The commandId parameter must match the command field in package.json
	let disposable = vscode.commands.registerCommand('helloworld.helloWorld', () => {
		// The code you place here will be executed every time your command is executed
		// Display a message box to the user
		vscode.window.showInformationMessage('Hello World from HelloWorld!');
	});

	context.subscriptions.push(disposable);
}

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

英語ですがコメントでいろいろ説明されているので、なんとなくやっていることが見えてきます。

このソースコードでは「activate」と「deactivate」という2つの関数が定義されており、それぞれ拡張機能が有効化されたときと無効化されたときに呼ばれます。

今回作成したHelloWorldの拡張機能では、コマンドパレットでこ「Hello World」を実行したときにactivate関数が実行されます。

activateが実行されるタイミングは拡張機能の種類によって違うのだと思いますが、詳しく調査はしていません。

そしてこのactivate関数の中でvscode.commands.registerCommandというAPIを使って、「helloworld.helloWorld」というIDで実行する処理を登録しています。

ここで実行する処理というのは、波括弧で囲まれた範囲で記述された処理で、具体的には「vscode.window.showInofrmationMessage」というAPIを使って「Hello World from HelloWorld!」というメッセージを表示することです。

つまり、これがコマンドパレットで「Hello World」を実行したときに行われる内容になります。

ちなみに後述のブレークポイント等を使って細かく動きを見ると、

  • コマンドパレットでの1回目の「Hello World」の実行
    activate関数が実行された後に、helloworld.helloWorldというIDで登録した処理の実行
  • コマンドパレットでの2回目の「Hello World」の実行
    helloworld.helloWorldというIDで登録した処理の実行

となり、activate関数が実行されるのは初回の実行時のみになります。

ここで疑問となるのは、

  • なぜコマンドパレットで「Hello World」を入力したときに、helloworld.helloWorldというIDで登録した処理が実行されるのか
  • そもそもactivateが実行される前に、なぜコマンドパレットに「Hello World」が現われるのか

だと思います。

その答えはpackage.jsonというファイルにあります。このファイルの「contributes」というキーは次のようになっています。

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

ここで「command」と「title」というペアで、「helloworld.helloWorld」というIDが「Hello World」という名前と結びつけられていることがわかります。

このためコマンドパレットで「Hello World」を入力すると、「helloworld.helloWorld」というIDで登録された処理が実行されるようです。

activate前にコマンドパレットに「Hello World」が現われるのもこのpackage.jsonのおかげとなります。

これらの仕組みがわかれば、新たなコマンドの追加などもできそうな気がしてきます。

デバッグをする

拡張機能は通常のプログラムと同様にデバッグすることができます。

一番わかりやすいのはブレークポイントとステップ実行だと思います。

まずextension.tsを開き、行番号の左側を左クリックしてみましょう。赤い○が表示されたらその行にブレークポイントが設定されていることになります。

ブレークポイントの設定

この画面の例では11行目と19行目にブレークポイントが設定されています。

この状態で「実行」→「デバッグの開始」を選択(あるいはF5キー)してみましょう。

そして新たに表示されたVS Codeのウィンドウのコマンドパレットで「Hello World」を実行してみましょう。

すると元のextension.tsを開いていたVS Codeのウィンドウがアクティブになり、11行目がハイライトされます。

ブレークポイントで停止

コマンドパレットで「Hello World」を実行したことから、extension.tsで定義しているactivate関すが実行され、11行目に設定していたブレークポイントで処理が一時停止しているという状態です。

このとき次のようなツールバーが表示されます。

デバッグ用ツールバー

このツールバーには6個のボタンが用意されており、ボタンの役割は左から

  • 続行
  • ステップオーバー
  • ステップイン
  • ステップアウト
  • 再起動
  • 停止

となります。

ここで「ステップオーバー」を2回実行してみます。すると19行目のブレークポイントで停止をせずに、22行目に到達します。

22行目でブレーク

これはactivate関数が実行された段階では、19行目の処理は「helloworld.helloWorld」というIDの処理として登録されただけで実行されていないからです。

ここで「実行」をすると次は19行目で停止します。

19行目でブレーク

今度はコマンドパレットで「Hello World」が入力されたので、それに対応したIDであるhelloworld.helloWorldの処理が実行されたからです。

左側のコールスタックをみると「_executeControbutedCommand」という関数から、この処理が呼び出されていることがわかります。

また、ここでは有効な情報が表示されていませんが、左側の変数のエリアを使うと、プログラム中の変数の値を確認することができます。

そして「実行」をすると、ShowInformationMessageのAPIが実行され、新たに表示されたVS Codeの方にメッセージが表示されます。

このようにVS Code上から拡張機能を実行することで、ブレークポイント等を活用したデバッグができることがわかります。

VS Codeへのインストール

拡張機能が問題ないことを確認したら次は拡張機能をパッケージングして、VS Codeにインストールできるようにしてみましょう。

拡張機能はVS Codeマーケットプレースに公開して、そこからインストールすることもできます。

今回はあくまでもテスト用の拡張機能なので、マーケットプレースは使用せず、ローカルにインストールする方法を採用します。

パッケージング

拡張機能をパッケージングするには「vsce (Visual Studio Code Extensions)」というツールを利用します。

このツールはnpmで導入可能です。今回はWSL2を利用しているので、WSL2側で下記のコマンドを実行します。

$ sudo npm install -g vsce

以降のパッケージングする際にエラーが出るので、拡張機能のディレクトリにあるREADME.mdは変更しておきましょう。

私は生成されたREADME.mdの中身をごっそり削って、次のような内容にしておきました。

# helloworld拡張機能

この拡張機能はテスト用の拡張機能です。

vsceのインストールに成功したら、続いて作成した拡張機能のディレクトリに移動してvsceコマンドを実行します。

$ cd ~/projects/first-extension/helloworld
$ vsce package

途中で

  • 「A ‘repository’ field is missing from the ‘package.json’ manifest file.」
  • 「LICENSE.md, LICENSE.txt or LICENSE not found」

という警告が出ますが、それぞれ「y」を入力して継続してしまってください。

最終的にhelloworld-0.0.1.vsixというファイルが生成されればパッケージング成功です。

インストール

次は作成されたパッケージ(vsixファイル)をインストールしていきましょう。

VS Codeのサイドパネルで拡張機能を選び、スリードットメニューから「VSIXからのインストール」を選択します。

VSIXからのインストール

ファイルを選べるようになるので、先ほどパッケージングしたVSIXファイルを選択します。

Remote-WSL拡張機能でWSL2と連携していれば、WSL2内のVSIXファイルも選択できます。

VSIXファイルを選択

インストールに成功すると次のようなメッセージが右下に表示されるので「今すぐ再度読み込む」を選択します。

拡張機能のインストール完了

これでインストール済みの拡張機能を確認すると「HelloWorld」という拡張機能がでてインストールに成功していることがわかります。

インストールした拡張機能

もちろんインストール完了後はコマンドパレットからこの拡張機能が提供する「hello worldコマンド」を実行可能です。

また、不要になればこの拡張機能画面からアンインストールすることができます。

まとめ

今回はVS CodeとWSL2の組み合わせでVS Codeの拡張機能が作成できるか試してみました。

Remote WSL拡張機能で連携できていれば、WSL2側にNode.jsを導入することで、拡張機能の開発ができることがわかりました。ステップ実行等のデバッグも可能です。

Linuxに慣れている方にはWindows上で環境を構築するより楽かもしれません。

コメント