VS Codeの拡張機能を自作しよう

突然ですが、皆さんは「テキストエディタ」と聞いて何を思い浮かべるでしょうか?

僕はもちろんVisual Studio Code (VS Code) です。 気がつくと僕の周りでも愛用者が増えており、今や「エディタといえばVS Code」という状況だったりします。

だがちょっと待ってほしい。

キミはまだVS Codeの真価を知らない!

── というわけで、今日はライバルに差のつくVS Codeの裏技をこっそり紹介しようと思います。

すべてのものは二度作られる

本題に入る前の導入として、少しだけもったいぶったばなしをしましょう。

故 スティーブン・R・コヴィー博士は、その著書の中で“すべてのものは二度作られる (All things are created twice.)”と述べました。

確かに、完成形を思いえがけないモノをいきなり作る事は難しいですから、僕らの日常業務もえてして二度手間の連続です。

  • プレーンテキストの議事メモを推敲してから、納品用に清書する
  • ロジックを設計してから、コーディング作業をする
  • 概念実証をしてから、プロダクトの量産を行う

── などなど。 これらは確かに、お仕事の成功率を上げるためには大切なことですが、反面、うまくいくことが分かりきった“二度目の作業”を退屈に感じることもしばしばあります。

そこで、一度目の成果物から、二度目の成果物を自動生成できれば、つねに創造的で挑戦的ないとなみだけに集中できるようになり、なおかつ仕事が早くなるので一石二鳥です。

果たして、そんな夢のような事が本当にできるのでしょうか。

退屈なことは○○にやらせよう

そこで満を持して登場するのが、VS Code拡張機能(の自作)です。

この時点ではまだピンと来ていない方も多いと思いますので、いくつか例を紹介しましょう。

例1: CFnテンプレートからパラメータシートを錬成する

ときどき、AWSのリソースをCFnCloud Formationで作ることがあります。 そこまではよいのですが、たまにExcel形式のパラメータシートを一緒に納品してほしい』と無茶を言われることがあります。

せっかく書き上げたCFnテンプレートをわざわざExcelに転記するのは非生産的で不毛な作業です。 が、僕はVS Code拡張機能を作って自動化しています。

YAML(左)からExcel形式のパラメータシート(右)が一瞬で錬成

正直、Excelのパラメータシートが何に使われるのかよくわからないですが、いかにも手間暇かかっていそうな、なんかスゴイモノを作った感が出ていますね。

例2: フォルダ階層図のTeXファイルを錬成する

環境構築マニュアルや開発手順書など、フォルダの階層図が必要になるシーンが往々にしてあります。

しかし、この階層図をドローツールで手描きしようとすると発狂するくらいめんどくさい。 経験ある方も多いのではないでしょうか。

そこで僕は、フォルダの階層図を簡単に錬成できるよう、これも拡張機能として実装しています。

テキストからフォルダ階層図を瞬時に錬成

厳密には、直接作図しているわけではなく、LaTeX で処理できるコードに変換しています。

LaTeX でこのクオリティの図を出力しようとするとそれなりに面倒ですが、そのあたりも自動化の魔法によってまるごと省力化されています。

──いかがでしょうか。

VS Codeは知っているけど、こんな使い方は知らないぞ! という方に、ぜひ本記事を読んでいただきたいと思います。

拡張機能を作ってみよう

だいぶん前置きが長くなりましたが、ここまでの作例を通してVS Code拡張機能開発に興味が湧いてきたのではないでしょうか。

今日はそんな皆さんのために、今日は『開いているテキストファイルの中身を、そっくりそのままExcelブックにエクスポートする』拡張機能を一緒に作ってみようと思います。

題材自体はあまり面白味はありませんが、応用次第で上述のパラメータシートのようなものを作れるようにもなります。

開発環境を作る

VS Code拡張機能を開発するには、Node.jsが必要です。 ここでは既にLTS版がインストールされているものとします。

より詳しい始め方は公式ドキュメントに譲るとして、本記事では簡易的な(最低限の)手順のみご紹介したいと思います。

以下のコマンドで、YeomanVS Code Extension Generatorをインストールします。

$ npm install --global yo generator-code
プロジェクトの作成

インストールしたYeomanを使って、拡張機能のプロジェクトを新規作成します。 以下のコマンドをターミナルに入力すると、対話的にプロジェクトを作成できます。

$ yo code

いくつか質問されるので、順番に答えていきましょう。 今回は、下図のように応答しました。

ひととおりの質問に答えると、プロジェクトが作成されます。 無事に作成に成功すると、Visual Studio Codeを起動するかと問われるので、Open with `code`を選択しましょう。


プロジェクトの確認

ここまでの手順で、拡張機能のプロジェクトが出来上がりました。 大量のファイルが作られましたが、まず見ておくべきは、srcの下にあるextension.tsというファイルです。

以下のコードのうち、// The code you place here will be executed every time your command is executedというコメントのあるブロックに何らかの処理を書くと、それが拡張機能を実行するたびに走ります(このあとすぐ編集します)。

初期状態では、Hello World from my-extension!というメッセージが表示されるだけになっています。

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 "my-extension" 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('my-extension.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 my-extension!');
  });

  context.subscriptions.push(disposable);
}

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

これで、プロジェクトの雛形ができました。 簡単でしたね。

試運転(デバッグ

あれこれコードを弄る前に、初期状態でどんな動きをするか確かめておきましょう。

Visual Studio Codeの左側のアクティビティバーのうち、デバッガのアイコンをクリックして、さらに、Run Extensionをクリックします。

すると、開発中の拡張機能がインストールされた状態の特別なVisual Studio Codeが新しいウィンドウで起動します。

画面の上部に[拡張機能開発ホスト]と表示されている点に注目しましょう。

それでは早速、先ほどの拡張機能を試してみましょう。

初期状態では、「Hello World」というコマンドで拡張機能が起動するように構成されているので*1、コマンドパレットに当該コマンドを入力すると、拡張機能を起動できます。

command/Ctrl + Shift + Pキーでコマンドパレットを表示し、Hello Worldコマンドを実行します。

拡張機能が起動すると、確かに右下にHello World from my-extension!というメッセージが表示されていることが確認できます。

これだけではいまいち何が嬉しいのか分からないので、もう少し実用的なツールを作ってみましょう。

Excel エクスポートツールを作る

ここからは、いよいよ拡張機能の処理を実際にコーディングしていきます。

繰り返しになりますが、ここで作る拡張機能は非常に基礎的なもので、『開いているテキストファイルの中身を、そっくりそのままExcelブックにエクスポートする』だけの機能となります。

とはいえ、開いているテキストファイルにアクセスする・Excelにデータを書き出すという技を修得すれば、たとえば議事録の所定フォーマットがExcelだったときでも必要以上に絶望せずに済みます。

また、package.jsonから依存ライブラリの一覧をExcel形式でまとめてほしいと言われたときにもサンプルをちょっと改造すればなんとかなります。

それではさっそく実装に移るのですが、今回はExcelファイルを扱うのでライブラリを追加する必要があります。 ターミナルに以下のコマンドを入力しましょう。

$ npm install xlsx-populate

次に、srcフォルダの直下(extension.tsと同階層)に、xlsx-populate.d.tsというファイルを新規作成し、以下を書き込みます。 これは、xlsx-populateライブラリに型定義ファイルが無いため、その場しのぎの措置でコンパイラを黙らせているのです。

src/xlsx-populate.d.ts

declare module 'xlsx-populate';

いよいよ本実装です。 先程のsrc/extension.tsを再び開き、以下のようにコーディングします。 テキストファイルの中身に対するアクセス方法が少々特殊なので、解説を要する箇所には適宜コメントを入れました(一方で、もともとあった大量のコメントは割愛しています)。

src/extension.ts

import * as vscode from 'vscode';
import xlsx from 'xlsx-populate';  // ★ 追加
import path from 'path';           // ★ 追加

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

  let disposable = vscode.commands.registerCommand('my-extension.helloWorld', async () => {

    // ★★★ 編集ここから ★★★

    // アクティブなエディタを取得(ここからテキストの中身にアクセスする)
    const editor = vscode.window.activeTextEditor;
    if (!editor) { return; }

    // 開いているファイルと同じフォルダを取得
    const currentFilePath = editor.document.uri.fsPath;
    const currentDirPath = path.dirname(currentFilePath);

    // 現在開いているファイルの拡張子を除いた名前を取得
    const currentFileNameWithoutExt = path.basename(currentFilePath, path.extname(currentFilePath));

    // 新規作成するファイルの絶対パスを作成('.xlsx' 拡張子を追加)
    const newFilePath = path.join(currentDirPath, `${currentFileNameWithoutExt}.xlsx`);

    // メモリ上にExcelブックを新規作成
    const workbook = await xlsx.fromBlankAsync();
		
    // Excelブックの編集
    for(let i = 0; i < editor.document.lineCount; i++) {
      // テキストを1行読み込む
      const line = editor.document.lineAt(i).text;

      // Excelのi+1行目・1列目にデータを書き込む
      // ※ Excelの番地は1始まりで指定する点に注意
      workbook.activeSheet().row(i + 1).cell(1).value(line);
    }
		
    // 編集済みのExcelブックの情報をバッファに出力
    const buffer = await workbook.outputAsync();
	
    // 新規作成したファイルにバッファを書き込む
    vscode.workspace.fs.writeFile(vscode.Uri.file(newFilePath), buffer);
		
    vscode.window.showInformationMessage(`Created a new file: ${newFilePath}`);
    // ★★★ 編集ここまで ★★★
  });

  context.subscriptions.push(disposable);
}

export function deactivate() {}

これを保存し、先程と同じ要領でデバッグ実行してみましょう。

適当なテキストファイルを開いて、拡張機能を発動させると、おそらくテキストファイルと同階層にExcelブックが新規作成され、テキストの中身が転記されているはずです。

まとめ

後半はだいぶ駆け足となりましたが、VS Code拡張機能を実際に作って動きを確認するところまで試してみました。

今回の話だけでもまだまだ深掘りの余地はあり、出力先のセル番地やフォント、背景色、罫線などを少しずつ書き換えると、出力結果がどんどんリッチになっていくでしょう。

また、Marpと組み合わせて客先向けの報告資料(定量報告と課題一覧)を半自動で錬成したり、長文を ChatGPT に推敲させたり、アイディア次第で仕事がどんどんラクになります。

そんなわけで、今日は VS Code の裏技のご紹介でした。

ではまた。

*1:本記事では触れませんが、この設定は後から変更できます。

Copyright (c) 2012 @tercel_s, @iTercel, @pi_cro_s.