Visual Studio Codeで秀丸の変換モジュールのような拡張機能の開発

# はじめに

みなさんはどのようなエディタを使用しているでしょうか？
私はVisual Studio Codeと秀丸エディタを使っています。
プログラミングするときはVisual Studio Codeがメインになりつつありますが、やはり秀丸エティは手放せません。秀丸エディタを手放せない理由はいくつかあります。

1. 秀丸エディタではキー操作を記録して再生することができるけど、Visual Studio Codeはできない。...と思ってましたが、[Keyboard Macro Beta](https://marketplace.visualstudio.com/items?itemName=tshino.kb-macro)という拡張機能を作ってる方がいらっしゃいました。こちらは試す価値がありそうです。
   https://marketplace.visualstudio.com/items?itemName=tshino.kb-macro
2. 秀丸エディタのために作った資産がたくさんある。
3. 秀丸エディタにお金を払ったのでもったいない。(貧乏性）

2で、秀丸エディタはマクロが使用できるのでマクロをたくさん作っているのだろう？と思った方もいるかも知れません。半分当たりです。でも半分は違っていて、私はマクロよりも変換モジュールを多用しているのです。

## 変換モジュールとは

秀丸エディタはVer5(2005年)から変換モジュールが使えるようになりました。(だいぶ前ですね。）
変換モジュールというのは、秀丸エディタの画面で選択した部分を入力として変換モジュールに渡し、変換モジュール内で加工した結果を呼び出し元に返すことで、選択されたテキストが変更できるという機能です。変換モジュールの実態はDLL(Dynamic Link Library)で、インターフェースを合わせれば自分で変換モジュールを作ることができます。

変換モジュールを自分で作れるようになったということで、練習がてらにUniqというモジュールを作ってみました。Linuxコマンドにあるuniqと似ていますが、あらかじめソートしておく必要がないところが違ってます。このUniqモジュールは、なんと変換モジュールの総ダウンロード数で第1位となってます。ま、登録されている変換モジュール数自体が少ないのですが。
https://hide.maruo.co.jp/lib/hmconv/uniq.html

下記ページで登録されている変換モジュールの一覧を見ることができます。
https://hide.maruo.co.jp/lib/hmconv/index.html
この中で私がよく使っているのは、全角半角変換とソートです。もちろん拙作のUniq、秀丸パイプもよく使っています。

## 秀丸パイプとは

変換モジュールの欠点はC/C++言語でDLLを作成しなければならないことです。（インターフェースがC言語の関数呼び出しとなっています。他の言語でもDLLが作れれば作れると思います。）
もっと気軽に変換モジュールの機能を使いたい、と思い作ったのが「秀丸パイプ」です。
https://hide.maruo.co.jp/lib/hmconv/hmpipe_104.html

秀丸パイプ自体は変換処理は行いません。何をやっているかというと、指定されたプログラムを起動して、秀丸から渡された入力データを起動したプログラムに標準入力として渡す。そしてプログラムが返した結果を標準出力で受け取り秀丸に返す、ということをやっています。つまり、秀丸とプログラムの橋渡しを行うプログラムとなっています。起動されるプログラムは何でもよいのですが、標準入力のデータを受け取って、結果を標準出力に書き出しておく必要があります。もちろんWindowｓのコマンドも使用できます。

この標準入力からデータを読み込み結果を標準出力に返す、というプログラムをたくさん作ってます。これが資産と言っているものです。当時はRuby好きだったので、ほぼほぼRubyで作ってました。
前置きが長くなってしまいましたが、Visual Studio Codeでも同じようなことができたら、これらの資産を活かせるのではないか、と思って作ったのが今回の拡張機能 [VSCode Pipe](https://marketplace.visualstudio.com/items?itemName=mtfuji.vs-code-pipe) です。

# さてと、何から手をつけようか？

- Visual Studio Codeの拡張機能を作るのは初めてです。
- JavaScriptは触ったことがありますが、だいぶ昔の話です。
- どうせならTypeScriptで実装してみたいな。
- Visual Studio Code拡張機能の開発をするには何を見ればよいのか？
  とりあえず下記を見つけました。
  - Extension API
    https://code.visualstudio.com/api
  - VS Code API
    https://code.visualstudio.com/api/references/vscode-api
  でも全部読むなんて、しかも英語だし...
  Extension APIの中に"Microsoft/vscode-extension-samples"へのリンクがあるではありませんか。とりあえずサンプルを真似してみよう、という方針にしました。
- ということで、サンプルを見ていきます
  https://github.com/microsoft/vscode-extension-samples
  サンプルがたくさんあります。たぶん70個くらいあります。
  サンプルのリンクを開くと、動作イメージのキャプチャがあるのでこれを参考にして私がやりたいことと似てるかどうかを判断できそうです。で、私が見つけたのがこのサンプルです。
  Document Editing Sample
  https://github.com/microsoft/vscode-extension-samples/tree/main/document-editing-sample
  エディタ上に"Hello"と入力すると、これを逆順にして表示するものです。

# 開発

## プロジェクトの作成

まずは練習でHello Worldを作ってみます。
おじさんが何を作るのか聞いてくるので、"New Extension (TypeScript)"を選択します。
```
$ yo code    

     _-----_     ╭──────────────────────────╮
    |       |    │   Welcome to the Visual  │
    |--(o)--|    │   Studio Code Extension  │
   `---------´   │        generator!        │
    ( _´U`_ )    ╰──────────────────────────╯
    /___A___\   /
     |  ~  |     
   __'.___.'__   
 ´   `  |° ´ Y ` 

? What type of extension do you want to create? 
❯ New Extension (TypeScript) 
  New Extension (JavaScript) 
  New Color Theme 
  New Language Support 
  New Code Snippets 
  New Keymap 
  New Extension Pack 
  New Language Pack (Localization) 
  New Web Extension (TypeScript) 
  New Notebook Renderer (TypeScript) 
```

他にも質問されるのでこんな感じで答えました。
```
? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? hello
? What's the identifier of your extension? hello
? What's the description of your extension? Hello World
? Initialize a git repository? No
? Bundle the source code with webpack? No
? Which package manager to use? npm
```
必要なモジュールをダンロードしてくれた後、code(Visual Studio Code)で開くか？と聞いてくるので、開いてもらいます。（至れり尽くせりです。）

プロジェクトが開かれた状態でF5を押すと拡張機能のデバッグが開始できます。新たに開かれたエディタでF1を押してコマンドパレットを開きます。ここに"Hello World"と入力すると、今回作成した拡張機能(Hello World)が出てくるので選択してEnterを押します。すると"Hello World from hello!"とメッセージが表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1106967/4630129f-e43e-1b7e-0fba-1645e0d0002f.png)

Hello Worldは動きましたが、まだソースを1つも変更してないですね。

## 初めてのTypeScript

上で見つけたサンプル [Document Editing Sample](https://github.com/microsoft/vscode-extension-samples/tree/main/document-editing-sample) を見ていきます。見るファイルはextension.tsです。このファイルを見ると、選択されたテキストの取得方法とその置換方法がわかります。この部分はそのまま使えそうです。

あと機能として実装したいことは、
- コマンドをあらかじめ登録しておいて、メニューから選択して実行したい。
- メニューからの選択以外に直接コマンドを入力できるようにもしたい。
- メニューから選択、もしくは直接入力されたコマンドを実行したい。
- 毎回コマンドパレットから呼び出すのは面倒なのでショートカットキーを割り付けたい。

です。

Visual Studio Code拡張機能の開発で調べたことについては後述します。ここではTypeScriptではまったことを書きたいと思います。

- for文の罠
  私は普段Pythonを使っています。リストの内容をここに取り出すときはこんな感じで書きます。
  ```python
  items = ["a", "b", "c"]
  for item in items:
      print(item)
  ```
  TypeScriptでも同じように書いてしまいました。
  ```typescript
  let items = ["a", "b", "c"];
  for (let item in items) {
    console.log(item);
  }
  ```
  このプログラムを実行すると
  ```
  0
  1
  2
  ```
  とコンソールに出力されます。
  調べてわかったのですが、for inで得られるのはリストのインデックスだったのです。
  上記プログラムを正しく動かすにはfor ofを使う必要がありました。
  ```typescript
  let items = ["a", "b", "c"];
  for (let item of items) {
    console.log(item);
  }
  ```

- 非同期処理
  メニューで選択された値を取得する、プログラムを起動して結果を取得する、などなど、これらはコールバックで関数を指定して非同期処理が行われます。非同期処理が行われることは私も知ってました。非同期処理についておさらいしていると今時の非同期処理はPromiseというもを使うらしいということを知りました。Promiseについては下記の記事がわかりやすいです。
  [【ES6】JavaScript初心者でもわかるPromise講座](https://qiita.com/cheez921/items/41b744e4e002b966391a)
  この記事では、更にawaitやasyncを使うと、もっとシンプルに非同期処理が書けると書いてあります。
  Promiseもawaitもasyncも知らない私にとっては、これらを習得するしかないですね。
  これは、はまったというよりも躓いたという感じでしょうか。

この章のタイトルは「初めてのTypeScript」としてますが、ここで紹介したfor文やPromise, await, asyncはJavaScriptの話で、TypeScript以前の話ということになります。つまり、私が最近のJavaScriptを知らなかっただけということがわかりました。。私がJavaScriptを触っていたのはIEのバージョンが一桁の時代でした。あれからいろいろ変わっていたのです。

そこでJavaScript再入門です。ここで紹介した記事などを参考に、とりあえずPromiseとawait, asyncを習得したつもりです。だ完全ではないですが、使える域には達したと思います。

## 拡張機能の開発でいろいろ調べたことなど

- メニューに表示する
  こちらの記事を参考にしました。
  [Visual Studio Codeのコマンドパレットでリスト(Quick Pick)を出す方法](https://clickan.click/vsc-list-api/)
- settings.jsonから値を取得する
  こちらの記事を参考にしました。
  [VSCode 拡張機能開発について](https://zenn.dev/yhsi/scraps/36dbb868d8322c)
- 外部プログラムを実行する
  child_processを使います。こちらの記事を参考にしました。
  [シェルコマンドを実行する方法(child_process)
](https://www.wakuwakubank.com/posts/728-nodejs-child-process/)
- 画面からコマンドを入力する
  showInputBoxを呼びます。こちらの記事を参考にしました。
  [QuickInputとInputBoxを利用してユーザー入力を受け付ける - Visual Studio Codeの拡張機能を開発する](https://blog.yucchiy.com/2021/05/vscode-quickinput-inputbox/)
- ショートカットから起動できるようにしたい
  package.jsonで定義します。こちらの記事が参考になりました。
  [Visual Studio Codeの拡張機能を作成する](https://atmarkit.itmedia.co.jp/ait/articles/1511/30/news030_2.html)

# そして公開

公開するにもいろいろ手順が必要です。Azure DevOpsにアカウントを登録し...とここでは書きませんが、下記の記事を参考にしました。
[VSCodeの拡張機能の公開手順](https://zenn.dev/ryo_kawamata/scraps/986c4466c02013)

で、できあがったのが下記です。

https://marketplace.visualstudio.com/items?itemName=mtfuji.vs-code-pipe

ソースはGitHubに置いています。
https://github.com/mahoutsukaino-deshi/vscode-pipe

使用していただいた方がいらっしゃいましたら、ご意見や感想などをいただけると助かります。

Enjoy Coding!