nikkie-ftnextの日記

イベントレポートや読書メモを発信

VSCode拡張開発 素振りの記:ハンズオンテキストを元に「歩夢」を「歩夢🎀」に置き換える拡張を自作し、コマンドとコードレンズを完全に理解

はじめに

私の夢を、一緒に見てくれる?」、nikkie です。

エディタ Visual Studio Code、通称「VSCode」、私は普段使いしています。
VSCode には豊富な拡張があり、特に Python 拡張には大変助けてもらっていますが、この拡張って自作できるんですよね。
VSCode の拡張どうやって作るんだろう?」と気になり、ハンズオンテキストに沿ったり脱線したりしながら素振りしてみました。
その模様を綴ります!

目次

できたもの:この拡張、どうかしてるぜ!👍

VSCodeMarkdown ファイルを編集しているとき、「歩夢」という文字列があると、マウスクリックでそこに 🎀 を追加できます1

歩夢 -> 歩夢 🎀 -> 歩夢 🎀🎀 -> ...

マウスクリックでラクラク emoji が追加できる!
無限に 🎀 が追加できます!
やばい、たーのしーー ⤴️
この発想、どうかしてるぜ!

Hello VS Code Extension ハンズオンテキスト

この無限 🎀 追加拡張の作り方がどこかのテキストに載っているわけではなく2、ハンズオンテキストを進める中で「実現できるな」と気づき爆誕させました!

このハンズオンはVSCode Conference Japan 2021で開催されました。
LSP(Language Server Protocol)を使った拡張については YouTube に動画も残っています3
ただいきなり LSP を使うのはハードルが高いと感じたため、GitHub リポジトリの方の基礎編「拡張機能の基礎を学びたい人向け」に取り組むことにしました。
TypeScript も触ったことすらないというレベルなので、一歩目は小さくしたかったのです。

拡張機能の基礎を学びたい人向け

https://github.com/vscodejp/handson-hello-vscode-extension/tree/bd882275b6802f9c3bcb5717ae0f32cbe2d89a60/docs/beginner

基礎編のコンテンツは 4 つ。

  1. 拡張開発に必要なファイル類を配置する
  2. Hello World 拡張を起動する
  3. スニペット拡張機能を作る
  4. ドキュメントを編集する拡張機能を作る

3 だけは手を動かさずに進め、4 を作った後に「ドキュメントの編集の内容を応用すればいけそう」と、上で紹介したどうかしている拡張を自作しました 🎀

環境構築

ハンズオンテキストは充実しており、環境構築も案内されます!
https://github.com/vscodejp/handson-hello-vscode-extension/blob/bd882275b6802f9c3bcb5717ae0f32cbe2d89a60/docs/00_prepare.md

以下を用意しました:

  • VSCode
    • ESLint 拡張
  • Node.js
    • Yeoman

Yeoman は VSCode 拡張開発の雛形を生成する(つまり、ディレクトリやファイル一式を配置する)ツールでした。
初めて知ったのですが、なんと Python を使った開発の雛形も作ってくれるようです。興味深い!

ハンズオンテキスト(基礎編)に沿った素振り

ファイル類の配置

yo codeで一発なんだ YO!
Yeoman すごいんだ YO!!

Hello World 拡張を起動

Yeoman で作成すると、デフォルトで Hello World 拡張が実装されています(src/extension.ts)。
起動を試み、開発環境が整っているか確認しました。

  1. F5 キーで拡張機能をビルド
    • 新しい VSCode のウィンドウが開く
  2. 新しい VSCode のウィンドウで F1 キー(コマンドパレット呼び出し)
  3. コマンドパレットから「Hello World」を実行
    • 新しい VSCode のウィンドウ右下に「Hello World」という通知が出ます 🙌

vscode.window.showInformationMessageに渡す文字列を変えて再ビルドすることで、新しい VSCode のウィンドウに反映されます。
表示する文字の変え方も分かりました!

vscode.commands.registerCommandメソッドで'hello-vscode.helloWorld'という識別子でコマンドを登録しています(処理自体はコールバック関数として渡しています)。
package.jsonにて、コマンドの識別子が「Hello World」というコマンドパレットでの表示と結び付けられていました!

  "contributes": {
    "commands": [
      {
        "command": "hello-vscode.helloWorld",
        "title": "Hello World"
      }
    ]
  },

基礎編メインの実装:ドキュメントを編集する拡張機能を作る

日付を挿入するVSCode拡張

https://github.com/vscodejp/handson-hello-vscode-extension/blob/bd882275b6802f9c3bcb5717ae0f32cbe2d89a60/docs/beginner/04_edit.md

基礎編で作る拡張機能は、VSCodemarkdownを編集中に、最初の行が見出し(#で始まる)であれば、日付を挿入するためのコードレンズのボタン「add date」を表示するというものです4

# s

という行から始まっているとすると、「add date」で

# 2022/09/11 s

に変わります(日付が挿入されました)。

日付を挿入するVSCode拡張を作る

src/extension.tsで実装し、新しい VSCode のウィンドウで動作確認します。
実装手順は以下

  1. package.jsonactivationEventsを設定
  2. 拡張機能起動時に自作のCodeLensProviderを起動する実装
    • vscode.CodeLensProviderを継承して定義
    • 拡張機能起動でactivate関数が呼び出される
    • activate関数で自作のCodeLensProviderを登録(vscode.languages.registerCodeLensProvider
  3. ドキュメントを解析してコードレンズを表示
    • 正規表現を使って「最初の行が見出し」を探す
    • 見つかった行についてコードレンズを作成(☆)
    • 「add date」が表示されるだけでクリックしても何も起こりません
  4. コードレンズをクリックしてドキュメントが編集できるようにする
    • "markdown-date.addDate"コマンドを登録(vscode.commands.registerCommand
    • コードレンズで"markdown-date.addDate"コマンドが実行されるようにする
    • コマンドの中身(コールバック関数)はコードレンズの range のテキストを読み取り、日付を加えたテキストに置き換えるという実装(editBuilder.replace

これでテキストと同じように動く拡張ができました!

上で示した例で最初の行が「# s」とすると、コードレンズの range に該当するのは「#」(空白含む)です。
ここに日付(と空白)を加えて# 20220911)「# s」を置き換えます。
すると結果は「# 20220911 s」と日付が追加されるというわけです!
一度コードレンズから「add date」してもmarkdownの見出しであることは変わらないので、何度でも日付を追加できちゃいます。

はじめてTypeScript(ほほえまエピソード)

TypeScript の開発でややとまどった点をご紹介。
3 の(☆)のところ、実装は以下のようになります。

const command = {
  title: "add oshi emoji",
  tooltip: "add oshi emoji",
};
codeLenses.push(new vscode.CodeLens(range, command));

ここでnew vscode.CodeLens(range, command)の command に赤線が!

Argument of type '{ title: string; tooltip: string; }' is not assignable to parameter of type 'Command'.

「赤線あるからビルドできない?」と不安に襲われましたが、新しい VSCode のウィンドウでの実行はできました。
赤線は 4 でオブジェクト5commandcommandというキーとその値(イベントの識別子)を追加することで解消します。
どうやら、CodeLens のイニシャライザの第 2 引数はCommandというインタフェースを実装しているかをチェックし、3 の時点では実装していないと判断されたため赤くなったようです。
4 で実装を追加すると、オブジェクトcommandCommandインタフェースで必須のtitlecommandも両方持つので、型チェックを通ったと理解しました。
期待されるプロパティを持っているかをインターフェースでチェックするというのは興味深いですね。

日付の代わりに、「歩夢」を見つけたら「歩夢 🎀」に置き換える!

日付の挿入は、正規表現を使って該当するパターンを検索し、置き換えを実施していました。
このことから、日付の挿入に限らず、「指定したパターンに別の文字列を追加できるということか!」とときめいちゃいました!
こうして、どうかしている VSCode 拡張が爆誕したのです。

日付の追加と同じロジックで「歩夢」を「歩夢🎀」に置き換えます。

例えば

「歩き出そうdreaming way」って文字通り"歩夢"じゃないですかー

連打すると

「歩き出そうdreaming way」って文字通り"歩夢🎀🎀🎀🎀🎀"じゃないですかー

たーのしーー!!

現状では、「歩夢歩夢」という行には対応できていません。
コードレンズは2つ表示されるのですが、どちらをクリックしても先の歩夢に🎀追加となりました。

終わりに

VSCodeの拡張の作り方が気になり、ハンズオンテキスト基礎編に沿って素振りしました。
コマンドとコードレンズは完全に理解した!

LSPが使えないと自由度低いのかなと思っていましたが、「基礎編」の内容だけでも結構色々とできそうです。
Python関係の拡張のソースをいくつか覗いているのですが、この延長で実装されている"お手本"も見つかりました。
今回のどうかしてるぜ拡張がmarketplaceに並ぶかどうかは未定としか言えませんが、VSCode拡張自作に向けて順調な滑り出しを切ったと思います。

ハンズオンテキスト制作者の皆さまに感謝申し上げます。

P.S. なぜ 🎀 を追加するの?

歩夢と🎀の関係、当然の疑問だと思います。
歩夢という文字列は、ニジガク(ラブライブ!虹ヶ咲学園スクールアイドル同好会)の上原歩夢ちゃんを指しています!
そしてラブライブ!のメンバーには対応するemojiがあるそうなんです。

アンサー:歩夢ちゃん=🎀なんだぜ。エイリアスなんだぜ!


  1. この emoji を追加する理由はアニメが絡むので本編では扱いません。全くピンとこない方は「Hello World」や「hoge」「fuga」みたいなものだと思ってください。界隈の慣例です。理由を知りたい方は(この脚注の上の)P.S.をどうぞ!

  2. といっても「悪魔の証明」だと思うので、知っている方いたらお知らせください!

  3. VSCode 拡張のサンプルリポジトリにもありました:https://github.com/microsoft/vscode-extension-samples/blob/133fa26af64ba8760559c5a06299953673d60763/codelens-sample/src/CodelensProvider.ts

  4. JavaScriptのオブジェクトは JavaScriptにおける連想配列でハマりました。全てをお話しします - nikkie-ftnextの日記 で転んで学びました