引き続きSolargraph
workspace
プロジェクトのディレクトリに対して1つのWorkspaceが割り当てられる。
library
- 1つのlibraryが1つのApiMapを持つ
- 1つのソースコードと対応している?
- 現在見ているソースコードという状態を持っているだけで、そうではないかも
- 1つのworkspaceが複数のlibraryを持つ?
- library belongs_to workspace という関係
- 要請に応じて違反や定義元を返したりする役割も持っている
- エディタ起動時、エディタのworkspaceが
Host#prepare_folders
で読み込まれる - Solargraph::Library.loadでそのディレクトリの情報が用意される
- Host#librariesに読み込んだ情報が登録される
- エディタからのリクエストに応じて、Host#librariesが検索される
Library#find_external_requires という実装があった。そのソースコード内の require
を集めているらしい。
bundlerの管理するgemのYARDはどう読み込まれているのか気になって調べてみた。 YardMapでrequire_from_bundleというのがあったので、Gemのソースは多分ここで読み込まれている。YardMapがどこから使われているかは不明。ApiMapがYardMapの参照を持っているので多分ApiMapが管理している。yard_mapは、definitionを調べるリクエストとtype checkから使われていた。
Workspace#generate_require_paths を見ていると、gemspecが存在する場合は、それを読んでrequire_pathsの内容からどのファイルを読むべきか推測するというコードが書かれていた。デフォルトではlibになる。
Workspace#initializeではそのディレクトリの配下のRubyのソースコードがほぼ全て読み込まれ、Solargraph::Sourceに変換される。
Solargraphで定数をgo to definitionしたときには候補が沢山表示されるなと思っていたが、これはRubyがオープンクラスできるためで、その定数をnamespaceとして利用している実装が候補に上がるためである。solargraphは第一候補として一番それらしいやつを挙げてくれるようなので、大体のケースではF12を二回押すだけで事足りる。
カーソル位置についての処理を担当するのがClipクラスらしい。
ApiMapというのは、いろんなところでソースコードの集合を扱うための実装で、1つのLibraryが1つのApiMapを持っていたり…するらしいが概念がよく分からない...がlibraryが複数のソースコードを扱う存在であると考えるなら妥当かも。
ApiMapが大きいやつで、SourceMapが小さいやつ…
最初のステップとして、@param
で仮引数の型が明示されている場合に、その仮引数の定義位置を返す、という機能を実装できるようにしてみるのはどうか。
- Solargraph::LanguageServer::Message::TextDocument::Definition 型を表すPinの配列からLSP用の位置情報を返す
- Host#definitions_at
- Library#definitions_at
- Clip#define カーソル位置のチェーン (一連のまとまり) から推測される型を表すPinの配列を返す
- Chain#define ほぼ同上
- Chain#infer_first_defined 型の計算が行われる中核部分
- Link#resolve 型を表すPinの配列を返す
chain.linksのlinksはchainの要素で、a.b.cの場合linksはa, b, cみたいな感じ。
ApiMapをchainあたりまで引き回しているが、これはdatabase connectionみたいなもので、実際のソースコードの情報を色々引いてくるにはこうするのが妥当。
inferが結構大変そう。 例えば、メソッドの本体部分からreturn valueになり得るものを探してそのnodeの取り得る型をinferするとか。 infer無しでYARDの型定義だけ探して無ければ諦めるならまだマシそう。
どこかで library.catalog が呼ばれて、そのタイミングで libarary.api_map の内容が更新される (ソースコードが読み込まれる?) と思うんだけど、いつどこで呼ばれるのか分かっていない。
CLIから呼ばれるときはそこで呼ばれてるんだけど、Language Serverから呼ばれるときは… Host#catalog で呼ばれていた。あとスレッドを立てて、0.01秒sleepしながら一生catalogを呼び出し続けていた。DidChangeWatchedFiles命令でも呼び出している。起動時にも呼び出していると思うのだが…これはその別スレッドが呼び出すのだろう。