【実装編】Visual Studio Code拡張機能の作り方
ここでは、Visual Studio Codeの拡張機能を実装するうえで必要な機能とその実装方法をカテゴリ別に紹介します。
コマンド
コマンドを登録する
コマンドを登録するにはpackage.json
にactivationEvent
とcontributes.commands
を定義します。activationEvent
には拡張機能をアクティベートする(=有効にする)イベントを記載します。この例では、vscodeBlogger.BloogerPostBlog
というコマンドが実行されたら拡張機能を有効にしています。
次にextension.ts
にコマンドを実行する関数を実装し、vscode.commands.registerCommand
で新しいコマンドを登録します。
-
package.json
{ "activationEvents": [ "onCommand:vscodeBlogger.BloggerPostBlog" ], "contributes": { "commands": [ { "command": "vscodeBlogger.BloggerPostBlog", "category": "VSCode Blogger", "title": "Bloggerへ記事を投稿する" } ] } -
extension.ts
export function activate(context: vscode.ExtensionContext) { let postBlog = vscode.commands.registerCommand('vscodeBlogger.BloggerPostBlog', () => { // ここにコマンドの処理を実装する }); context.subscriptions.push(postBlog); } export function deactivate() {}
プログラムからコマンドを実行する(引数や戻り値のないコマンド)
vscode.commands.executeCommand
を使うとコマンドをプログラムから実行することができます。この例ではアクティブなエディタにコメントを追加する組み込みコマンドeditor.action.addCommentLine
を実行しています。
-
package.json
{ "activationEvents": [ "onCommand:extension.addComment" ], "contributes": { "commands": [ { "command": "extension.addComment", "category": "Hello world", "title": "コメントを追加する" } ] } -
extension.ts
export function activate(context: vscode.ExtensionContext) { let disposable = vscode.commands.registerCommand('extension.addComment', () => { vscode.commands.executeCommand('editor.action.addCommentLine'); }); context.subscriptions.push(disposable); } export function deactivate() {}
プログラムからコマンドを実行する(引数や戻り値のあるコマンド)
引数や戻り値のあるコマンドを実行した場合、vscode.commands.executeCommand
はPromiseを返すのでasync/await(もしくはthen/catch)を使って非同期処理にする必要があります。この例では、関数や変数の定義位置を取得する組み込みコマンドvscode.executeDefinitionProvider
を実行しています。
-
package.json
{ "activationEvents": [ "onCommand:extension.printDefine" ], "contributes": { "commands": [ { "command": "extension.printDefine", "category": "Hello world", "title": "定義位置を表示する" } ] } -
extension.ts
export function activate(context: vscode.ExtensionContext) { let disposable = vscode.commands.registerCommand('extension.printDefine', async () => { const activeEditor = vscode.window.activeTextEditor; if (!activeEditor) { return; } const definitions = await vscode.commands.executeCommand<vscode.LocationLink[]>( 'vscode.executeDefinitionProvider', activeEditor.document.uri, activeEditor.selection.active ); if (definitions) { for (const definition of definitions) { console.log('<定義位置>'); console.log(`ファイル: ${definition.targetUri.path}`); console.log(`${definition.targetRange.start.line}行目 ${definition.targetRange.start.character}文字目から`); console.log(`${definition.targetRange.end.line}行番号${definition.targetRange.end.character}文字まで`); } } }); context.subscriptions.push(disposable); } export function deactivate() {}
コマンドURI
コマンドURIを使用するとコマンドを実行するリンクを作成することができます。この例では、JavaScriptファイルでホバーにコメントを追加するコマンドリンクを表示しています。
- package.json{ "activationEvents": [ "onLanguage:javascript" ], }
- extension.tsexport function activate(context: vscode.ExtensionContext) { vscode.languages.registerHoverProvider( 'javascript', new (class implements vscode.HoverProvider { provideHover( _document: vscode.TextDocument, _position: vscode.Position, _token: vscode.CancellationToken ): vscode.ProviderResult<vscode.Hover> { const commentCommandUri = vscode.Uri.parse(`command:editor.action.addCommentLine`); const contents = new vscode.MarkdownString(`[コメントを追加](${commentCommandUri})`); contents.isTrusted = true; return new vscode.Hover(contents); } })() ); } export function deactivate() {}
メッセージ
メッセージを表示する
メッセージを表示させる場合は、vscode.window.showInformationMessage
を使います。
vscode.window.showInformationMessage('Hello World!');
メッセージを表示する(ボタン付き)
ボタン付きのメッセージを表示させ、ユーザーに選択させる場合は、vscode.window.showInformationMessage
の第2引数以降に、ボタンに表示する文字列を入力します。ユーザーが押したボタンは、コールバック関数の引数として参照できます。
vscode.window.showInformationMessage('Hello World!', 'OK', 'Cancel')
.then((selected) => {
console.log(selected);
});
メッセージのアイコンを変える
アイコン別にAPIがあるので、必要なアイコンのAPIを使います。
- 情報アイコン
vscode.window.showInformationMessage
- 警告アイコン
vscode.window.showWarningMessage
- エラーアイコン
vscode.window.showErrorMessage
ステータスバーにメッセージを表示する
ステータスバーにメッセージを表示するには、vscode.window.setStatusBarMessage
を使います。第2引数にはメッセージを非表示にするまでの時間(ms)を指定します。
vscode.window.setStatusBarMessage('Hello VSCode', 3000);
ステータスバーにメッセージを表示する(終了条件付き)
ステータスバーにメッセージを表示し、非表示の条件を動的に決定したい場合は、vscode.window.setStatusBarMessage
の第2引数に判断をするPromiseを指定します。
vscode.window.setStatusBarMessage('Hello VSCode', new Promise((resolve, reject) => {
setTimeout(() => {
resolve(true);
}, 5000);
}));
メニュー
メニューを追加する(エディタ/タイトル)
エディタの右上にメニューを表示する場合はcontributes.menus.editor/title
を定義します。
以下はエディタの右上にアイコンで表示する例です。
- package.json{ "contributes": { "commands": [ { "command": "extension.helloWorld", "title": "Hello World", "icon": { "light": "./out/resources/star-1.svg", "dark": "./out/resources/star-2.svg" } } ], "menus": { "editor/title": [ { "command": "extension.helloWorld", "group": "navigation", "when": "resourceLangId == markdown" } ] } } }
それぞれの定義の意味は以下になります。
contributes.commands
- メニューはコマンドを関連付けるので、コマンドの定義が必要です。
contributes.menus
editor/title
はエディタタイトルのメニュー定義であることを表します。command
にはメニューに関連付けるコマンドを指定します。group
には表示するグループ(位置)を指定します。navigation
は「...」の外にメニューを表示することを意味します。when
はメニューの表示条件を表します。上記例では、Markdownファイルの場合にメニューを表示します。
例の図を見ると分かりますが、メニューに表示されるアイコンやテキストはcontributes.commands
で定義したものです。contributes.menus
はメニューの表示位置や条件、関連付けるコマンドを定義するものになります。
詳細は、「Contributors.menus」を参照ください。
サブメニューを追加する(エディタ/タイトル)
エディタの右上に表示するサブメニューを表示する場合はcontributes.menus.editor/title
を定義します。
- package.json"contributes": { "commands": [ { "command": "extension.helloWorld", "title": "Hello World", "icon": { "light": "./out/resources/star-1.svg", "dark": "./out/resources/star-2.svg" } } ], "menus": { "editor/title": [ { "submenu": "submenu1", "group": "4_edit", "when": "resourceLangId == markdown" } ], "submenu1": [ { "command": "extension.helloWorld", "group": "navigation", "when": "resourceLangId == markdown" } ] }, "submenus": [ { "id": "submenu1", "label": "VSCode submenu", "group": "1_modification@1" } ] }
それぞれの定義の意味は以下になります。(前章のメニューと同じ項目は省略しています)
contributes.menus
editor/title
はエディタタイトルのメニュー定義であることを表します。command
の定義はありません。サブメニューの親(この例では「VSCode submenu」)にはコマンドを関連付けないので定義しません。submenu
は関連付けるサブメニューのidを指定します。後ほど記載するcontributes.submenus
で定義するidを指定します。
submenu1
はサブメニューのアイテム群の定義です。サブメニューに関連付けるコマンドはここで定義します。この例では「VSCode submenu」内に1つのメニューアイテムを定義していませんが、複数定義することも可能です。
contributes.submenus
- サブメニューの親(この例では「VSCode submenu」)を定義します。
id
はサブメニューのidを定義します。label
はサブメニューの親の表示文字列を定義します。
ファイル操作
現在アクティブなファイルのパスを取得する
現在エディタでアクティブになっているファイルのパスを取得する場合は、vscode.window.activeTextEditor
から取得します。
const editor = vscode.window.activeTextEditor;
const file_path: string = editor?.document?.fileName || "";
console.log(file_path);
ファイルをエディタで開く
任意のファイルを開く場合、openTextDocument()
とshowTextDocument()
を使用します。
vscode.workspace.openTextDocument(path).then(doc => {
vscode.window.showTextDocument(doc);
});
ファイルの読み込み
ファイルの読み込みには、vscode.workspace.fs.readFile
を使用します。ただ、readFile()
での読み込みはバイト配列(Uint8Array)での読み込みなので、文字列で読み込みたい場合は、Uint8Arrayで読み込んでから変換が必要です。
以下の例では、vscode.workspace.fs.readFile
で読み込んだ後、Buffer.from()
でUint8Arrayを読み込み、toString()
で文字列に変換しています。
(async () => {
// ファイルを読み込む
const file_path: string = "c:\\content.md"
const blob = await vscode.workspace.fs.readFile(vscode.Uri.file(file_path));
console.log(blob);
// テキストへ変換
const text = Buffer.from(blob).toString(); // デフォルトはutf-8
console.log(text);
})();
ファイルの書き込み
ファイルの書き込みには、vscode.workspace.fs.writeFile
を使用します。readFile()
同様writeFile()
での読み込みはバイト配列(Uint8Array)での書き込みなので、文字列で書き込みたい場合は、文字列をUint8Arrayに変換してから書き込みが必要です。
以下の例では、vscode.workspace.fs.readFile
で読み込んだ後、Buffer.from()
でUint8Arrayを読み込み、toString()
で文字列に変換しています。
(async () => {
// テキストをUint8Arrayに変換
const text: string = "あいうえお";
const blob: Uint8Array = Buffer.from(text);
// ファイルへ書き込む
const file_path: string = "c:\\test.txt"
await vscode.workspace.fs.writeFile(vscode.Uri.file(file_path), blob);
})();
エラーの対応方法
TS2724
原因
'WebviewViewProvider' という名前のエクスポートされたメンバーが '"vscode"' に含まれていません。ts(2724)
このようなエラーが発生した場合、使用しているパッケージのバージョンが古い可能性があります。上記例では、WebviewViewProviderは比較的新しいvscodeの機能ですが、vscodeのパッケージが古くWebviewViewProviderの定義が見つからずエラーが発生しています。このような場合は、使用しているパッケージのバージョンを更新する必要があります。
対応
個々のパッケージを一つずつチェックする方法もありますが、個々のパッケージのバージョンを確認するのは非常に手間がかかります。ここでは一括で最新バージョンに更新する方法を記載します。
- パッケージの最新バージョンをチェックするnpx -p npm-check-updates -c "ncu"
- package.jsonを更新するnpx -p npm-check-updates -c "ncu -u"
- パッケージをインストールするnpm install
参考サイト
まとめ
最後まで読んでいただきありがとうございます。 また読んでくださいませ。 そんじゃーね。