xtextによるVSCodeのエクステンションに進捗表示を追加した


概要

前回の記事に続けて、サーバー側の処理に時間がかかる場合を勝手に想定し、VSCode側に進捗状況を表示することに対応しました。これもLSPを学ぶいいネタになると思ったので、取り組んでみました。
結果は、LSPのみならず、利用するライブラリのバージョンについても意識/管理しなければならない、オープンソースを利用したアプリ開発での必須項目にふれる、いい機会となりました。
これらが誰かのお役に立てば幸いです。

開発環境

  • Windows10 Pro 1909
  • VSCode 1.47
  • JDK 1.8
  • Xtext 2.24(前回の記事までは2.20。理由は後述します)
  • LSP4J 0.10.0(前回の記事までは0.9.0。同上)

課題

サーバー側の処理に時間がかかる場合に、VSCode側に進捗状況を表示するようサーバーからVSCodeへ通知し、ユーザーが進捗状況を把握できるようにすること、とします。

詳細

ポイント

ポイントのひとつが、利用するアプリケーションやライブラリのバージョンでした。調査した結果、進捗状況を表示するためのバージョンは以下だとわかりました。

  • LSP 3.15以上
    LSP仕様に進捗状況に対応の記載があります

  • VSCode 1.42以上
    VSCodeのリリースノートにLSP3.15が利用可能と記載があります

  • LSP4J 0.10.0以上
    LSP4JのGitHubに0.9.0からLSP3.15対応とあるものの、進捗は除くとカッコ書きがあることから、0.10.0以降と考えていいでしょう

  • Xtext 2.24以上
    Xtextのリリースノートに2.24からLSP4Jの0.10.0へ更新と記載があります

ということで、次でアップデートを試みます。

Xtextのバージョンを2.24へ

ベースにした時点での、Xtext Visual Studio Code Exampleでは、Xtextは2.20を使うよう指定されていました。今日現在では、バージョン2.23を使うように指定が更新されていますが、進捗表示に必須のバージョン2.24ではありません。指定箇所は、

()
configure(subprojects.findAll { it.name.startsWith('org.xtext') }) {
    ext.xtextVersion = '2.20.0'
()

のように、build.gradleファイル内にあります。
動けばラッキーくらいで、無理矢理2.24を使うように指定を変更してみることにします。

()
configure(subprojects.findAll { it.name.startsWith('org.xtext') }) {
    ext.xtextVersion = '2.24.0'
()

のように変えました。
結果は、エラーなくビルドが完了し、VSCodeが起動。簡単な操作ではあるものの、従前と変わらずに動いていそうです。ついでに、GradleがDLしたjarファイルを確認します。jarファイルはvscode-extension-self-contained\src\mydsl\libに格納されます。VSCodeで当該フォルダを見てみると、

でハイライトしたとおり、Xtext系は2.24、LSP4J系は0.10.0が当該フォルダに入っていることがわかります。
進捗表示の作業が継続できそうです。

進捗表示は2段階

次は進捗表示機能の、自分の理解を兼ねた、簡易テストです。
キーボードのいずれかのキー押下毎に呼び出される、didChangeメソッドに次のコード

private int progress = 0;   //これはメソッド外で宣言

()

//確認を簡単にするために、ProgressParamsインスタンスの第1引数は”0”のみ使用するとしています
if (progress == 0) {
    org.eclipse.emf.common.util.URI uri = org.eclipse.emf.common.util.URI.createURI(params.getTextDocument().getUri());
    client.createProgress(new WorkDoneProgressCreateParams(Either.forLeft("0")));
    WorkDoneProgressBegin wdpr = new WorkDoneProgressBegin();
    wdpr.setTitle("processing: " + uri.lastSegment());
    wdpr.setMessage("prepare...");
    wdpr.setPercentage(new Integer(progress));
    client.notifyProgress(new ProgressParams(Either.forLeft("0"), wdpr));
    progress = 1;
} else if (progress > 0 && progress < 100) {
    WorkDoneProgressReport wdpr = new WorkDoneProgressReport();
    wdpr.setMessage("processed " + progress + "%");
    wdpr.setPercentage(new Integer(progress));
    client.notifyProgress(new ProgressParams(Either.forLeft("0"), wdpr));
    progress += 3;
} else {
    WorkDoneProgressEnd wdpr = new WorkDoneProgressEnd();
    wdpr.setMessage("done");
    client.notifyProgress(new ProgressParams(Either.forLeft("0"), wdpr));
    progress = 0;
}

を加えて動作確認しました。
キーを連打したときの様子はスクリーンショットですが


な感じです。
なお、VSCodeは進捗状況をアピールしてきませんでした。表示するには、画面右下のベルの形のアイコンをクリックし、NOTIFICATIONSというウィンドウを表示する必要があります。
上記の検討の結果、進捗表示のユーザーへの見え方/見せ方としては、

  1. ビジー表示(1枚目の画像)
  2. プログレスバー表示(2枚目の画像)
  3. 終了(表示が消える)

の順に表示の段階を経ることがわかりました。それぞれをWindowsでのファイルコピー時に当てはめると、ビジー表示はコピー前の準備でコピー完了までに必要な時間の計算中の状態、プログレスバー表示は実際にコピー作業を実行中でコピーが終わるたびに進捗状況が進む状態、完了後にウィンドウが閉じる、でしょうか。

基本的な動作は以上です。
最後にテストの過程で気づいたことを覚書として残しておきます。

Begin時の%指定は0が楽

NOTIFICATIONに表示開始を依頼する際、

WorkDoneProgressBegin wdpr = new WorkDoneProgressBegin();
wdpr.setPercentage(new Integer(0));

のようにして、setPercentageメソッドの引数で初期の%を指定可能(上記は0)です。ただし、最初に数値の指定を怠ると、WorkDoneProgressReportで何度更新を通知しても、ビジー表示からプログレスバー表示に変わりません。
また、初期の%を

WorkDoneProgressBegin wdpr = new WorkDoneProgressBegin();
wdpr.setPercentage(new Integer(50));

のように0以外(上記は50)と指定する事もできました。
こうすると、WorkDoneProgressReportで通知した値が初期の値以上になってはじめてビジー表示からプログレスバー表示に変わることがわかりました。その際、プログレスバーの表示量は、初期の50を起点としているようで、50で0%、51で1%と増え、100で50%となります。また、51%にあたる101を指定してもプログレスバーが増えませんでした。おそらく何らかの用途を想定してこのような動作になっているのかと思うものの、わかりにくので、初期値は0の決め打ちが分かりやすいと考えます。

まとめ

まとめると、VSCodeの進捗状況を表示する機能の特徴や利用する上では、

  • 進捗表示開始後、右下のベルの形のアイコンが微妙に変化し、進捗表示を含む何かしらの通知が来ていることが分かる
  • 進捗表示には、ビジー表示とプログレスバー表示の2段階がある
  • 0でビジー表示、1以上の指定でプログレスバー表示となり、100以上を指定してもプログレスバー表示のまま変わらない
  • 進捗表示を消すには、WorkDoneProgressEndインスタンスをクライアントに通知する必要がある。クライアントへの通知を忘れると表示されたままとなるので注意

です。
参考までに関係するクラスを列挙すると、

です。

今回は進捗表示の初期検討のため、流用を意識したものとはしていません。
今後、進捗表示/更新を別スレッド化を取り入れて、流用しやすい設計を目指してみたいと思います。