VS Code拡張API - DocumentSymbolProvider

#概要

「アウトライン」にシンボル表示させるためにVS Code拡張を作った。`DocumentSymbolProvider` を実装することによって機能を実現しているが、「サンプルはないしドキュメント読んでもこれを実装したらどうなるのかわからない」という状況になった。手探りした結果、後で参照したくなる程度にはわかったので書く。

要点を述べると、まず `xxxxProvider` はLanguage Server Protocolのサーバと同じ機能を提供するようになっている(多分)。LSPのクライアントはVS Codeで、ユーザが作る拡張ではない。なのでVS Codeが決めたタイミングでしか動かない。例えば`DocumentSymbol`の表示(サイドバーのエクスプローラー)を保存時に更新してほしくても、してくれない(ファイル編集すると更新される)。

そして `DocumentSymbolProvider` を実装するとアウトライン表示とファイル内シンボル検索ができるようになる。

あとカーソルジャンプはドキュメントの選択範囲(の1番目)を幅ゼロにセットした後その範囲を表示させればいい(`setCursorPosition()` みたいなものはなかった)。具体的には `vscode.window.activeTextEditor` の `selection` をセットして `revealRange()` を呼ぶ。

#経緯

##シンボルが欲しい

例えばreST(reStructuredText)編集時などにシンボル情報を使いたい。

* 「アウトライン」に表示されてほしい(Markdown編集時のように見出しがツリーになる)
* 前方・後方のシンボル(見出し)にカーソル移動したい(検索経由で移動していた)
* `Ctrl+Shift+o` (もしくは `Ctrl+p` のち `@`)からセクション名称で検索したい(気がしたが、そうでもなかった)

要はこれと

![2019-05-12_dbg01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223517/b7d21273-4c58-4ff1-f171-f31661c83688.png)

これが欲しい(画像は出来上がったものから採った)。

![2019-05-12_dbg02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223517/9531d6d1-6ce3-f1de-7bdc-c07c8283319d.png)

既存のreST用の拡張を試したが上記の機能はない。あと全体的に合わないので使っていない(ので貢献できない)。

reST以外でも、マイナーな言語で、開発環境が物足りない場合でもちょっとファイル編集が楽になる。要はVimのTagbarの機能が一部実現されれば嬉しい。

##`DocumentSymbolProvider`を実装すればいい

これを見た: [visual studio code - How to enable "Go to symbol" with a custom language in vscode? - Stack Overflow](https://stackoverflow.com/questions/46045245/how-to-enable-go-to-symbol-with-a-custom-language-in-vscode)

##Ctagsに頼る

tagsファイルがあればシンボルの名前と位置が判る。自分でファイルを分析しなくて良い一方、リネーム、参照リストアップ関係は無理になった。

##DocumentSymbolProvider

MicrosoftがVS Code拡張サンプルを提供してくれているが、この機能についてはない。リネームもない。

[microsoft/vscode-extension-samples: Sample code illustrating the VS Code extension API.](https://github.com/Microsoft/vscode-extension-samples)

なのでドキュメントを読みながら手探りでやった。

[DocumentSymbol - VS Code API | Visual Studio Code Extension API](https://code.visualstudio.com/api/references/vscode-api#DocumentSymbol)


#わかった

##`provideDocumentSymbols()`を実装する

基本は上記スタックオーバーフローの回答と同様にやればいいが、`DocumentSymbol[]` を返すようにした。シンボルごとに `DocumentSymbol` を作ってそれを配列にすれば良い。`children` プロパティをセットしてやれば親子関係がツリー表示される。

##コンストラクタの引数

`detail` はアウトライン表示のとき名称の右側に表示される。

`range` (定義範囲)と `selectionRange` (シンボルの位置)はそれらしくセットしてみたが `range` の意味が判らなかった。ソースコードを見ると値のバリデーションを行っているので多分意味があるが、「定義範囲に別のシンボルがあれば親子関係になる」とか「定義プロバイダを実装しなくてもその場で定義表示される」とかは起こらなかった。

##タイミング

実装した `provideDocumentSymbols()` が動作開始するタイミングは、ファイルを開いたタイミング **ではなく** 、アウトラインが表示されたときか、`Ctrl+Shift+o` などでシンボル検索したときだった(他にもあるかもしれない)。要は使う時まで始まらず、「エクスプローラー」が非表示のままファイルを開いた場合は、シンボルがまだない状態にある。「前方・後方のシンボルへ移動」コマンドの挙動がおかしくなって気づいた(~~まだ対応していない~~)。

##SymbolInformationではツリーにならない

アウトライン表示がツリーにならなくていいなら `SymbolInformation[]` を返しても動作する(初めはこちらで作った)。

動作するが、疑問があった。定義ファイル(とそれをもとにしたAPIドキュメント)では `provideDocumentSymbols()` の返り値は `ProviderResult<SymbolInformation[] | DocumentSymbol[]>` ということになっている一方で、この関数の宣言を見ると `DocumentSymbol` の方しか存在しない。定義ファイルが間違っている(Structural Typingによってたまたまあるいは意図されて機能している？)のか？

結局「定義ファイルおかしいかもしれません」というイシューを書いて、返事をもらった。

[DocumentSymbolProvider's return type definition is maybe incorrect · Issue #73093 · microsoft/vscode](https://github.com/Microsoft/vscode/issues/73093)

`containerName` はクイックアウトラインで表示されるための文字列でツリー構造のためのものではないということだった(名前重複したら困るとは思った)。

あと「クイックアウトライン」がコマンドパレットで名前の右の表示のことならばその通りで、イシューでリンクを貼ったブランチでも確認できていた。`DocumentSymbol` は `containerName` 持ってないのになぜその表示が出るかというと、vscode/modes.ts にある宣言(を見ないと判らないが)では `containerName` を持っているのでどこかでセットされている。ツリー表示には `children` が必要で、`range` は関係ない(変数定義が関数の定義範囲に存在するから親としてセットする、とかはない)。

定義ファイルのことは触れられなかったが動作的には問題なさそう(分かりにくいが)、ということで「間違ってないなら問題ないです」と残した。

##終わりに

これは多分単に言語サーバ等外部プログラムへの入力を作って出力を受け取るためのもので、実際はあまりVS Code拡張自体では処理しないのではないかと思った。

作った拡張自体は現在2言語で利用できていて嬉しい。

[symbol-by-ctags - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=PrsPrsBK.symbol-by-ctags)

嬉しいが、正直「どうせやるならその言語専用のサービスを作った方がいい」と感じた。なので「何にもないよりちょっと嬉しい」くらいの出来のままにする(要は凝らない)のが正しい姿かもしれない。

###追記

実装してみても意味の分からない機能がやはりあり(あちこちにある `Range` は何なのか)、1つ覚えれば簡単ということはないけど、ワークスペース全体のシンボルならドキュメントのシンボルをセットアップするときに(少なくとも一部は)作れる。`WorkspaceSymbolProvider` を実装すれば良い。ということでついでにやった。

![2019-05-12_dbg03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223517/4d2e45e9-2fc9-7607-cb1d-c630fba56cfc.png)

「この辺のことをやれば大体こんなことができる」という参考になれば幸いと思ったので画像だけですが載せます。