0
0

More than 3 years have passed since last update.

xtextによるVSCodeのExtensionでライブプレビュー

Last updated at Posted at 2020-09-17

概要

前回の記事xtextによるVSCodeのExtensionをビルドしてみたでは、GitHubからCloneしたソースコードからVSCodeのExtensionをビルドした際のメモをレポートしました。
今回は、出来上がったExtensionをライブプレビューの観点で掘り下げたいと思います。
掘り下げていく文章形式のため、結論めいたものやコードは用意してません。

ライブプレビュー

ライブプレビュー(リアルタイムプレビュー、単にプレビューとも呼ばれるようです)といえば、編集した内容が即座に別の形式に変換され、さらみ変換結果が画面で確認できることを指すと考えています。
VS CodeでMarkdownをプレビューするには?でも示されているとおり、VSCodeはデフォルトでMarkdownドキュメントをプレビューすることをサポートしているので、簡単にできます。
Qiitaの記事を編集する機能も同様ですね。
これが前回の記事でビルドしたExtensionでもできます。

xtextによるExtensionでは

まずは、VScodeが起動している状態から始めます。
現状では残念ですが、このExtensionではウィンドウ分割と同時にPreview表示(Ctrl+K+V)をサポートしていないので、次に示すやり方とする必要があります。
image.png
画面の右上に赤枠で囲ったアイコンがあります。このアイコンにマウスカーソルを合わせると、Split Editor Rightとを表示されるのが確認できます。ちなみに、altキーを押しながらアイコンにマウスカーソルを合わせると、Split Editor Downとを表示されます。芸が細かい。
altキーを押しながらアイコンクリックし、下側のウィンドウをアクティブにしてsrc-genフォルダにあるaGreeter.javaファイルを開いてください。
image.png
ここまでで、上下に分割されたエディタエリアにファイルが表示され、このような表示になったと思います。
a.mydslファイルのどの行でもいいのですが、ここでは最終行のYouに続けてuを複数回入力すると、
image.png
のように、a.mydslの編集結果が即座にaGreeter.javaにSystem.out.println("Hello Youuuu !");と反映されました。
ファイル生成を伴うのでMarkdownの編集時の雰囲気と異なりますが、ライブプレビューの要件が変更内容が即座に所望のドキュメントまたは画面に反映されることと考えると、これで十分に要件を満たしていると考えることができます。

なぜライブプレビューのような動作になるのか?

一方で、なぜビルドしたExtensionはこのような動作になるのでしょうか。
org.xtext.example.mydsl.generator.MyDslGeneratorクラスのdoGenerateメソッドが呼び出されていることは明らかです。

スタックトレース
java.lang.Throwable
    at org.xtext.example.mydsl.generator.MyDslGenerator.doGenerate(MyDslGenerator.java:25)
    at org.eclipse.xtext.generator.GeneratorDelegate.doGenerate(GeneratorDelegate.java:43)
    at org.eclipse.xtext.generator.GeneratorDelegate.generate(GeneratorDelegate.java:34)
    at org.xtext.example.mydsl.ide.server.build.IncrementalBuilder$InternalStatefulIncrementalBuilder.generate(IncrementalBuilder.java:425)
    at org.xtext.example.mydsl.ide.server.build.IncrementalBuilder$InternalStatefulIncrementalBuilder.lambda$launch$2(IncrementalBuilder.java:284)
    at com.google.common.collect.Iterators$6.transform(Iterators.java:783)
    at com.google.common.collect.TransformedIterator.next(TransformedIterator.java:47)
    at com.google.common.collect.FluentIterable.copyInto(FluentIterable.java:791)
    at org.eclipse.xtext.build.ClusteringStorageAwareResourceLoader.executeClustered(ClusteringStorageAwareResourceLoader.java:68)
    at org.eclipse.xtext.build.BuildContext.executeClustered(BuildContext.java:54)
    at org.xtext.example.mydsl.ide.server.build.IncrementalBuilder$InternalStatefulIncrementalBuilder.launch(IncrementalBuilder.java:265)
    at org.xtext.example.mydsl.ide.server.build.IncrementalBuilder.build(IncrementalBuilder.java:493)
    at org.xtext.example.mydsl.ide.server.build.IncrementalBuilder.build(IncrementalBuilder.java:470)
    at org.xtext.example.mydsl.ide.server.ProjectManager.doBuild(ProjectManager.java:106)
    at org.xtext.example.mydsl.ide.server.ProjectManager.doInitialBuild(ProjectManager.java:97)
    at org.xtext.example.mydsl.ide.server.BuildManager.doInitialBuild(BuildManager.java:185)
    at org.xtext.example.mydsl.ide.server.WorkspaceManager.refreshWorkspaceConfig(WorkspaceManager.java:180)
    at org.xtext.example.mydsl.ide.server.WorkspaceManager.initialize(WorkspaceManager.java:153)
    at org.xtext.example.mydsl.ide.server.LanguageServerImpl.lambda$initialize$0(LanguageServerImpl.java:233)
    at org.xtext.example.mydsl.ide.server.concurrent.WriteRequest.run(WriteRequest.java:38)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:748)
java.lang.Throwable
    at org.xtext.example.mydsl.generator.MyDslGenerator.doGenerate(MyDslGenerator.java:25)
    at org.eclipse.xtext.generator.GeneratorDelegate.doGenerate(GeneratorDelegate.java:43)
    at org.eclipse.xtext.generator.GeneratorDelegate.generate(GeneratorDelegate.java:34)
    at org.xtext.example.mydsl.ide.server.build.IncrementalBuilder$InternalStatefulIncrementalBuilder.generate(IncrementalBuilder.java:425)
    at org.xtext.example.mydsl.ide.server.build.IncrementalBuilder$InternalStatefulIncrementalBuilder.lambda$launch$2(IncrementalBuilder.java:284)
    at com.google.common.collect.Iterators$6.transform(Iterators.java:783)
    at com.google.common.collect.TransformedIterator.next(TransformedIterator.java:47)
    at com.google.common.collect.FluentIterable.copyInto(FluentIterable.java:791)
    at org.eclipse.xtext.build.ClusteringStorageAwareResourceLoader.executeClustered(ClusteringStorageAwareResourceLoader.java:68)
    at org.eclipse.xtext.build.BuildContext.executeClustered(BuildContext.java:54)
    at org.xtext.example.mydsl.ide.server.build.IncrementalBuilder$InternalStatefulIncrementalBuilder.launch(IncrementalBuilder.java:265)
    at org.xtext.example.mydsl.ide.server.build.IncrementalBuilder.build(IncrementalBuilder.java:493)
    at org.xtext.example.mydsl.ide.server.build.IncrementalBuilder.build(IncrementalBuilder.java:470)
    at org.xtext.example.mydsl.ide.server.ProjectManager.doBuild(ProjectManager.java:106)
    at org.xtext.example.mydsl.ide.server.BuildManager.internalBuild(BuildManager.java:211)
    at org.xtext.example.mydsl.ide.server.WorkspaceManager.lambda$didChangeFiles$1(WorkspaceManager.java:226)
    at org.xtext.example.mydsl.ide.server.LanguageServerImpl.lambda$runBuildable$16(LanguageServerImpl.java:459)
    at org.xtext.example.mydsl.ide.server.concurrent.WriteRequest.run(WriteRequest.java:40)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:748)

Eclipse環境で生成したxtextプラグイン内にあるGeneratorは、DSLファイルの保存に同期してdoGenerateメソッドが実行されるようになっており、キー押下に同期してdoGenerateメソッドは実行しません。だからこそ、以前書いた最近よく見るライブプレビューなアプリをEclipseで作ってみた・準備編一連の記事で、ライブプレビューができる仕組みを作ってみたのですが…。
さて、その流れをdoGenerateメソッドでのコールスタックから見てみると、org.xtext.example.mydsl.ide.server.LanguageServerImplクラスのdidChangeメソッドから続く一連の処理において、org.xtext.example.mydsl.ide.server.WorkspaceManagerクラスのdidChangeFilesメソッド

didChangeFilesメソッド
    public Buildable didChangeFiles(List<URI> dirtyFiles, List<URI> deletedFiles) {
        BuildManager.Buildable buildable = buildManager.submit(dirtyFiles, deletedFiles);
        return (cancelIndicator) -> {
            List<IResourceDescription.Delta> deltas = buildable.build(cancelIndicator);
            afterBuild(deltas);
            return deltas;
        };
    }

を経由し、さらに、buildable.buildメソッドの呼び出しの先に、org.xtext.example.mydsl.generator.MyDslGeneratorクラスのdoGenerateメソッドが呼ばれるようになっていました。
なお、LanguageServerImplクラスのdidChangeメソッドは、キー押下によってドキュメントの内容が変更された際にLanguage Server Protocol(LSP)が呼び出すことになっています。LSPのdidChangeメソッドのReferenceはこちら
よって、キー押下に同期してファイルが保存される、ということがわかりました。
なお、doGenerateメソッドについてのデバッグの詳細はデバッグ環境に書いたのでそちらを参照してください。

保存時に同期するためのヒント

GitHubの変更履歴にあるcdietrichさんはxtextのDeveloperの1人で、しかも相当にxtextに詳しいであろう人がなぜEclipseのxtextプラグインとは違う動きを実装してしまったのか。この記事でも同様の事象を止められないか質問されているのですが、cdietrichさんによる直接の解決策は提示されていません。しかし、同記事にあるリンク先にヒントがありました。
問題提起のタイトルこそ違うものの、内容を読むと同一原因と思われる現象が、このGitHub上ですでに解決していそうです。そこで、この記事およびGitHubの変更を確認して、キー押下の同期から保存時の同期への変更に着手したいと思います。
詳細は別の記事に書く予定です。

補足

簡単ですけど、本文中で述べたことの詳細や技術に関して、触れておこうと思います。

デバッグ環境

ソフトを開発する上で、コードを読んで意味を理解する、動きを予想する、動かした結果と過程から理解へフィードバックする、は基本サイクルのひとつですが、VSCodeでExtensionを動作させている間は結果だけが明確にわかるものの、過程についてはわかりません。

Xtext serverにログが出る

にも関わらず、なぜdoGenerateメソッドが呼ばれているタイミングが分かったのでしょうか。
答えは偶然です。
どこかにヒントがないか、とVSCodeを舐め回すようにクリックしていたところ、赤枠で示したプルダウンにXtext Serverがあり、それを選択したところ、javaの例外スロー時のスタックトレースがそこに表示されていました。
image.png

printfデバッグ

であれば、ということで、こちらExceptionなくStack trace出力を参考に、doGenerateメソッドの呼び出し時に意図的にスタックトレースを表示するようにしたところ、予想通りコンソールにスタックトレースが得られました。
なお、俗にいうprintfデバッグを行うには、標準エラー出力すなわち、System.err.printlnメソッドの呼び出しで可能なことも棚ぼたでわかり、最低限のデバッグ環境は整いました。
0から1になったことでだいぶ改善されたものの、いつまでも古典的なデバッグ方法に頼るのも心もとないし、効率もよくありません。やはりインスタンスレベルでの確認やブレークポイントを張りたいのです。さらに言えば、VSCode用のExtensionを作っているのだからVSCode上でデバッグが可能にならないか、少しずつ検討中です。

LSP

公式サイトはこちらです。
Language Server Protocolの略で、マイクロソフトが開発した技術です。
エディタの入力補完を含む機能実装において、実装言語と実現機能を切り離して自由度を上げる、再利用を可能とする、が目的の様です。
正直すごいなと思っていますが、現状はVScode用の様ですね。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0