【2023年】これからブログを始める人へおすすめの書籍

Kissy

【実装編】Visual Studio Code拡張機能の作り方

作成: 更新:
コマンド

Visual Studio Code拡張機能の作り方

ここでは、Visual Studio Codeの拡張機能を実装するうえで必要な機能とその実装方法をカテゴリ別に紹介します。

コマンド

コマンドを登録する

コマンドを登録するにはpackage.jsonactivationEventcontributes.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

コマンドURIを使用するとコマンドを実行するリンクを作成することができます。この例では、JavaScriptファイルでホバーにコメントを追加するコマンドリンクを表示しています。

  • package.json
    { "activationEvents": [ "onLanguage:javascript" ], }
  • extension.ts
    export 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の定義が見つからずエラーが発生しています。このような場合は、使用しているパッケージのバージョンを更新する必要があります。

対応

個々のパッケージを一つずつチェックする方法もありますが、個々のパッケージのバージョンを確認するのは非常に手間がかかります。ここでは一括で最新バージョンに更新する方法を記載します。

  1. パッケージの最新バージョンをチェックする
    npx -p npm-check-updates -c "ncu"
  2. package.jsonを更新する
    npx -p npm-check-updates -c "ncu -u"
  3. パッケージをインストールする
    npm install

参考サイト

まとめ

最後まで読んでいただきありがとうございます。 また読んでくださいませ。 そんじゃーね。

関連記事

SPONSORED LINK
SPONSORED LINK