【VSCode】複数の文字列を検索・置換する拡張機能を作った話

# 動機

VSCodeを含めたテキストエディタには、通常、検索・置換の機能が付属しています。
例えば、以下のようなJSONを作成したいとします。

```json:JSON
[
  {
    "lang": "Python"
  },
  {
    "lang": "Ruby"
  },
  {
    "lang": "JavaScript"
  },
  {
    "lang": "C#"
  },
  {
    "lang": "Java"
  },
  {
    "lang": "Rust"
  },
  {
    "lang": "Haskell"
  }
]
```

このJSONの材料となる元データとして、以下のデータを持っているとします。

```text:元データ
Python
Ruby
JavaScript
C#
Java
Rust
Haskell
```

このとき、手で直打ちしてJSONを作成するのは、面倒ですし間違いが発生するかもしれません。
データ数がもっと多い場合は更にそうでしょう。
置換をうまく使って作成したいところです。
（検索・置換の話ですので、jqを使え！ というのはナシにしてください…）

まあ、このくらいであれば、正規表現置換を使えば楽勝ですね。

```text:検索文字列
^(.*)$
```

```text:置換文字列
{\n  "lang": "$1"\n},
```

```text:結果
{
  "lang": "Python"
},
{
  "lang": "Ruby"
},
{
  "lang": "JavaScript"
},
{
  "lang": "C#"
},
{
  "lang": "Java"
},
{
  "lang": "Rust"
},
{
  "lang": "Haskell"
},
```

あとは、最終行のカンマを削除し、インデントを調整し、最初と最後に角カッコを入力すれば完成です。

さて、ここで追加の要件が発生しました。
このJSONに、以下のURLを追加する必要があります。

```text
https://www.python.jp/
https://www.ruby-lang.org/ja/
https://developer.mozilla.org/ja/docs/Web/JavaScript
https://docs.microsoft.com/ja-jp/dotnet/csharp/
https://java.com/ja/
https://www.rust-lang.org/ja
https://www.haskell.org/
```

はい、詰みです。
先ほどのJSONに、検索・置換機能を使ってこのデータを挿入することはできません。
やるとしたら、複数行入力機能を使用し、最初のデータにURLを連結し、最初から加工をやり直すしかありません。

```text
Python, https://www.python.jp/
Ruby, https://www.ruby-lang.org/ja/
JavaScript, https://developer.mozilla.org/ja/docs/Web/JavaScript
C#, https://docs.microsoft.com/ja-jp/dotnet/csharp/
Java, https://java.com/ja/
Rust, https://www.rust-lang.org/ja
Haskell, https://www.haskell.org/
```

```text:検索文字列
^(.+), (.+)$
```

```text:置換文字列
{\n  "lang": "$1",\n  "url": "$2"\n},
```

```text:結果
{
  "lang": "Python",
  "url": "https://www.python.jp/"
},
{
  "lang": "Ruby",
  "url": "https://www.ruby-lang.org/ja/"
},
{
  "lang": "JavaScript",
  "url": "https://developer.mozilla.org/ja/docs/Web/JavaScript"
},
{
  "lang": "C#",
  "url": "https://docs.microsoft.com/ja-jp/dotnet/csharp/"
},
{
  "lang": "Java",
  "url": "https://java.com/ja/"
},
{
  "lang": "Rust",
  "url": "https://www.rust-lang.org/ja"
},
{
  "lang": "Haskell",
  "url": "https://www.haskell.org/"
},
```

置換文字列がカオスになっています。
この程度なら全然良いのですが、追加の要件が発生するたびにカオス度が増してゆきます。

私の職業は事務（データ入力関係）なので、テキストデータの作成も仕事のうちですが、数千行もあるようなテキストに他のデータをちまちまと挿入してゆくような不毛な仕事が発生することがあり、さすがにやっていられないので、VSCodeの拡張機能を作ることにしました。

# Vector Replace

VSCodeの拡張機能「Vector Replace」を作成しました。

[Vector Replace](https://marketplace.visualstudio.com/items?itemName=tana-gh.vector-replace) ([GitHub](https://github.com/tana-gh/vector-replace))

![vector-replace-preview](https://tana-gh.github.io/vector-replace-media/window-replacing.gif)

# 先ほどの例の場合

以下のJSONにURL情報を挿入します。

```json:JSON
[
  {
    "lang": "Python"
  },
  {
    "lang": "Ruby"
  },
  {
    "lang": "JavaScript"
  },
  {
    "lang": "C#"
  },
  {
    "lang": "Java"
  },
  {
    "lang": "Rust"
  },
  {
    "lang": "Haskell"
  }
]
```

```text
https://www.python.jp/
https://www.ruby-lang.org/ja/
https://developer.mozilla.org/ja/docs/Web/JavaScript
https://docs.microsoft.com/ja-jp/dotnet/csharp/
https://java.com/ja/
https://www.rust-lang.org/ja
https://www.haskell.org/
```

以下の画像のように入力します。
まず、url属性とダミーの値を挿入します（これは通常の検索・置換でもできます）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53668/7edc3307-6fa0-6ae9-d168-cd8504b19020.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53668/d7efdd9f-15c8-a31f-29e3-4c477db801a2.png)

次に、実際のURLで置換します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53668/d99ff61c-3b3b-225f-6f96-3f474e36e386.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53668/d1882778-53f8-4048-dd89-a5bd80ce6013.png)

これで完成です。
JSONであればjqコマンドでもできますが、色々な種類のテキストに対応できるような置換機能が欲しかったのです。

# 動作について

![vector-replace-abc](https://tana-gh.github.io/vector-replace-media/window-abc.png)

「Search pane」に検索文字列を入力します。
各行は独立しており、各行の文字列がそれぞれ1つの検索文字列となります。
上の画像では「Search pane」に「a」「b」「c」の3つの検索文字列が入力されています。

これらの検索文字列は、エディタ上で「順番に」検索されてゆきます。
まず「a」を検索し、見つかった「a」の位置からは「b」を検索し、見つかった「b」の位置からは「c」を検索します。
そのため、画像のように先頭の「a」「b」「c」のみが色でマークされ、検索されていることが分かります。

「Replace pane」には置換文字列を入力します。
検索文字列と同様、各行が独立しており、各行がそれぞれ1つの置換文字列となります。
上の画像では「Replace pane」に「1」「2」「3」の3つの置換文字列が入力されています。

置換文字列も、検索された文字列を「順番に」置換してゆきます。
画像の例では、最初の「a」が「1」に、次の「b」が「2」に、次の「c」が「3」にそれぞれ置換されます。

その他、オプション設定によって動作が変化しますが、基本的にはこのような動きになります。

# オプション設定について

この拡張機能には、チェックボックスの設定項目が付随しています。
順に紹介してゆきます。

### Selection Search

選択範囲検索です。
これにチェックを入れると、エディタ上の選択範囲内のみを検索対象とします。
選択範囲が無い場合は全体から検索します。

### Use regular expression

正規表現検索・置換です。
これにチェックを入れると、検索文字列として正規表現が使用されます。
同時に、置換文字列として検索時のキャプチャ文字列（\$1、\$2、...）が使用できます。

### Capture whole

各検索文字列全体をキャプチャします。
「Use regular expression」が有効なときのみ動作します。
検索文字列が「abcde」だったとすると、「(abcde)」と同じ意味となり、置換時に「$1」で全体のキャプチャを使用できます。

### Ignore case search

大文字小文字の区別を無視します。

### Ignore bang search

「!」を検索文字列の先頭に付加することで、その検索文字列を無効にできます。
検索文字列のコメントアウトのような使用目的となります。

### Ignore bang replace

「!」を置換文字列の先頭に付加することで、その置換文字列を無効にできます。
置換文字列のコメントアウトのような使用目的となります。

### Ignore empty search

検索文字列の空行を無視します。
この設定は常にONであり、OFFにすることはできません。
空文字列を検索しても意味が無いためです。

### Ignore empty replace

置換文字列の空行を無視します。
これにチェックを入れると、置換文字列が空であるものは無視されます。
チェックを入れなければ、空文字列への置換が可能です。

### Loop search

検索時は検索文字列を順に検索してゆきますが、これにチェックを入れると「Search pane」の最終行まで検索が完了した際、再び先頭行の検索文字列で検索が試みられます。
「a」「b」「c」という検索文字列であれば、「a」「b」「c」「a」「b」「c」「a」...のように検索され続けます。

### Loop replace

「Loop search」と同様、「Replace Pane」の置換文字列が最終行に達した際、再び先頭行の置換文字列で置換が試みられます。

### Just search

「a」「b」「c」が検索文字列だったとすると、エディタ上に「abcab」というテキストがあれば全ての文字が検索されます。
しかし、「Just search」が有効であれば「abcab」のうち、最初の「abc」のみが検索され、最後の「ab」は検索されません。
「Search pane」の最終行まで検索がうまく行った場合のみ、検索結果として扱われます。
これと同時に「Loop search」が有効な場合、エディタ上に「abcabcab」というテキストがあれば、「abcabc」までが検索され、最後の「ab」は検索されません。

### Matrix search

これにチェックを入れると、検索文字列が「順番に」検索される、というルールが無くなり、各検索文字列をエディタ上のテキスト全体からグローバル検索します。
「a」「b」「c」という検索文字列があれば、まず「a」をテキスト全体から検索できる限り検索し、次に「b」をテキスト全体から検索、次に「c」をテキスト全体から検索、という動作になります。

# 効果

うちの部署ではデータ入力を行なっていますが、ExcelのテーブルやCSVなどを扱います。
これらのデータを、指示データを参考にしてパート社員がコツコツと手入力し、別のパート社員がダブルチェックの意味で目視確認する、といった業務が普通に行われています（私もパート社員です）。
この拡張機能により、そういった作業の一部では、数時間かかる作業を5～10分程度で完了させることができました。
少なくとも、私の業務に関して言えば、この拡張機能は恐るべき有用性でした。

また、プログラミングにおいても大量のテキストデータを作成したり、同じようなスクリプトを大量に生成したい場合が存在します。
そのようなケースでも、数千～数万行のデータを柔軟に用意することが可能でした。

# おわりに

同じような思いをしている方に届いてほしい拡張機能です。
是非お使いください。