引き続きSolargraph

workspace

プロジェクトのディレクトリに対して1つのWorkspaceが割り当てられる。

library

  • 1つのlibraryが1つのApiMapを持つ
  • 1つのソースコードと対応している?
    • 現在見ているソースコードという状態を持っているだけで、そうではないかも
  • 1つのworkspaceが複数のlibraryを持つ?
    • library belongs_to workspace という関係
  • 要請に応じて違反や定義元を返したりする役割も持っている
  1. エディタ起動時、エディタのworkspaceが Host#prepare_folders で読み込まれる
  2. Solargraph::Library.loadでそのディレクトリの情報が用意される
  3. Host#librariesに読み込んだ情報が登録される
  4. エディタからのリクエストに応じて、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 で仮引数の型が明示されている場合に、その仮引数の定義位置を返す、という機能を実装できるようにしてみるのはどうか。

  1. Solargraph::LanguageServer::Message::TextDocument::Definition 型を表すPinの配列からLSP用の位置情報を返す
  2. Host#definitions_at
  3. Library#definitions_at
  4. Clip#define カーソル位置のチェーン (一連のまとまり) から推測される型を表すPinの配列を返す
  5. Chain#define ほぼ同上
  6. Chain#infer_first_defined 型の計算が行われる中核部分
  7. 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命令でも呼び出している。起動時にも呼び出していると思うのだが…これはその別スレッドが呼び出すのだろう。