外部コマンドを実行する JetBrains IDE プラグイン開発の注意点

最近、Protocol Buffer Linter プラグインをリリースした。

0.1.1 まではリント結果の反応が少し不安定で遅かった。 0.1.2(2019.07.06 時点でアップロード後審査待ち)でそこを高速化して安定するように修正できたので、方法などをメモしておく。

まず、外部コマンドを実行するプラグインの場合は、ExternalAnnotator というクラスを利用することが推奨されている。 実装者は ExternalAnnotator 内で外部コマンドを実行して該当する行・列に warning などを表示する。 フックになる ExternalAnnotator の各メソッド自体は IDE のデーモンが編集中のファイルを引数に渡して呼び出してくれる。

0.1.1 では素直にそのファイルパスを外部コマンド(以下リンター)に渡して結果をエディタに反映させていた。 ただ、どうも反応性が不安定でちょっと遅いときがあるなと感じていた。 ドキュメントによると、他のエディタ機能が処理的に優先するため実行には delay があると説明されているので、それかなと思いつつ解決したいのでフォーラムに投稿した。

どうも不安定みたいな挙動は仕様ではなさそうということがわかったので、もう少しちゃんと見直したり他の関連チケットも見てみたところ、エディタ上の編集内容と実際のファイルの同期がとれていない段階で ExternalAnnotator が呼ばれているというのに気づいた。 確かにデバッグするとそうなっていたので、これが原因。

ExternalAnnotator にはファイルと一緒に編集中の内容も引数として渡されるので、ファイルの代わりに編集中の内容を一時ファイルに書き出してそれを使うように修正したところ、リアルタイム性が格段に向上した。

  • github.com
  • ちなみに、Intellij が用意している VirtualFile クラスを経由した書き込みは ExternalAnnotator 内から行おうとするとエラーになるので、java.nio.file.Files を使って直接作成・コピーしている。

呼び出している protolint は標準入力に対応していないので今回はこういう実装にしたが、一時ファイルにいちいち書き出すより標準入力に渡したほうが効率的なので、また修正するかもしれない。

ちなみに、scss-lint-plugin も編集中の内容を一時ファイルに書き出してからリンターに渡している。ただ、こちらは編集ファイルが置いてあるパスに一時ファイルを作っているので、トラブルになっている。 システム標準のテンポラリーディレクトリに作ったほうが無難だと思う。

まとめ

外部コマンドを編集中のファイルに対して作用させたいといったプラグインを開発する場合、編集中の内容と実際のファイルが同期される前にプラグイン処理が開始される可能性がある。 そのため、編集中の内容をコマンドの標準入力とするか、編集中の内容を一時ファイルに書き出してその一時ファイルをコマンドに渡すようにした方がいい。