はじめに
最近, TypeScript 1.5関連のエントリが少しずつ上がってきてるけど, このエントリはその中で最も誰得?となること間違いなし!
最初に断っておくが、このエントリを読んで得するのは、これから何かしらのエディタ(EclipseとかEmacsとか秀丸とか自分の信じている神に従え)でTypeScript向けのPluginを作ろうと思っている人限定である。
「他人の作ったpluginなんて使う気にすらならないぜ!自分で実装するぜ!」って奴は, こんなもん読まなくても自分で何とかしそうな気がプンプンする.
さて, 先日 別のエントリにて記載したが, TypeScript v1.5.0 alpha版公開に合わせて, TSServerを利用したVim plugin tsuquyomiを作成&公開した.
このエントリでは, tsuquyomiを作成する上で身についたTSServerの基礎知識を、備忘録も兼ねて書き連ねていこうと思う.
TSServerとは
TSServerはTypeScript 1.5からbundleされたエディタ向けのサーバであり, tsserver
コマンドで動作する.
本家のwiki にも記載がある通り, TSServerはLanguage ServiceにエディタやIDEがアクセスするためのアダプタの位置づけである.
詳細な機能は後述するが, TSServerを使うと下記機能をエディタから利用可能となる:
- 編集しているソースのcompile error取得
- ClassやInterfaceの定義情報取得
- リファクタリング(識別子の名称変更)
- 補完候補の取得
- etc...
特徴的なのは, APIのインターフェイスに標準入出力を採用しているため, エディタ/IDEの側ではプロセスとSTDIN/STDOUTを扱う仕組みさえあればよく, Node.jsによる実装は一切必要ないという親切設計な点.
TSServerを利用した各種Plugin
- VisualStudio Code : TypeScriptの連携機能にはバンドルされているTSServerが利用されている. MicroSoft謹製.
- TypeScript-Sublime-Plugin : Sublime向けplugin. MicroSoft謹製.
- tsuquyomi : 拙作Vim plugin.
TSServerの使い方
メッセージの書式
Requestは, 次のようなJSONフォーマットで組み立ててTSServerの標準入力に流し込む形となる.
{"type": "request", "seq": 0, "command": "open", "arguments": {"file": "sample1.ts"}}
-
type
:"request"
固定. 省略しても動く. -
seq
: リクエストのシーケンス番号. クライアント側で適宜インクリメントして利用する. 省略しても動く. -
command
: requestの種別. 利用可能な文字列は後述. -
arguments
: commandの引数. command毎に異なる.
実行例
うだうだ書くより実例で示した方が早いのでサンプルをば.
まず, TSServerに読み込ませるサンプルのソースコードを作成する.
module SampleModule {
export interface ISome {
name: string;
}
export class SomeClass implements ISome {
constructor(public name: string) {}
}
}
次に, TSServerの命令を用意する(インタラクティブにやってもいいけど,面倒なのでファイルにしておく)
{"type": "request", "seq": 0, "command": "open", "arguments": {"file": "sample1.ts"}}
{"type": "request", "seq": 1, "command": "definition", "arguments": {"file": "sample1.ts", "line":6, "offset":37}}
内容である程度察しが付くと思うが, 上記のcommandは下記を表している.
- open command で先に用意したsample1.tsを開かせ,
- definition commandで sample1.ts上の6行37列目に書いてある
ISome
の定義箇所を取得.
これをtsserverで実行すると,
tsserver < definition.json
下記の標準出力が得られる.
Content-Length: 171
{"seq":0,"type":"response","command":"definition","request_seq":1,"success":true,"body":[{"file":"sample1.ts","start":{"line":2,"offset":3},"end":{"line":4,"offset":4}}]}
interface ISome
の定義は, sample1.tsファイルの2 行3 列目〜4行4列目にありまっせ、と正しく定義箇所情報が返却された.
APIs
TypeScriptレポジトリのsrc/server/session.tsを読むと一覧が載っているが, TSServerに用意されたcommandは以下の通り:
command | type | 機能概要 |
---|---|---|
brace | response | ファイルのカーソルから見て, brace matchingする箇所を返す({ に対応する} の場所等). 一般的なエディタであれば, デフォルトで持っている機能であり, いらない子. |
change | none | ファイルの変更箇所をTSServerに通達する. |
close | none | TSServerでファイルを閉じる. |
completions | response | ファイルのカーソル(lineとoffsetで指定)位置における補完可能な単語の一覧を取得. |
completionEntryDetails | response | completions commandで取得した補完情報の詳細を取得. |
configure | none | インデント時のtab幅等, フォーマットに必要な設定を行う. tsconfigやcompilerOptions的な内容ではないので注意. |
definition | response | ファイルのカーソル上のsymbolについて, そのsymbolの定義箇所を取得する. |
exit | none | tsserver自体を終了する. |
format | response | ソースをフォーマットする?使ってないのでよくわからん |
formatonkey | response | これも使ってないのでよく分からん. |
geterr | event | コンパイルエラー情報を取得する. 現状, 唯一のevent発火API(後述). |
navbar | response | ファイルの見出しを作成する. IDEのサイドメニューに表示するような一覧情報を作るのに便利. |
navto | response | キーワード検索を行う. |
occurrences | response | カーソル上のIdentiferの出現箇所を取得する. referencesと似ているが, referencesは「どのようなcontextで参照しているか」まで取得可能であるのに対し, occurrencesは出現している場所情報のみを返却する. |
open | none | ファイルをTSServer上で開く. |
quickinfo | response | ファイルのカーソル上の情報を取得. |
references | response | ファイルのカーソル上のsymbolについて, そのsymbolを参照している箇所を既にTSServerでOpenしたファイルから検索し, 参照箇所の一覧を返す. |
reload | none | ファイルをリロードする. change と違い, 新ファイルで旧ファイルの内容を全置き換えするイメージ. |
rename | response | ファイルのカーソル上に存在するsymbol(identifier)を別名に置き換える場合に, 同時に置換すべき箇所の一覧を返す. リファクタリング用の機能を作るのに便利. |
saveto | none | デバッグ用command. TSServerが認識しているfileの内容をダンプする. reload, change commandで, TSServer上のファイル内容を変更しまくった時, 「あれ, TSServerが認識しているファイル内容ってどないやねん」ってなったときに利用する. |
signatureHelp | response | ファイルカーソル上のメソッド呼び出しについて, 引数情報等の詳細を取得. |
typeに"none"と記載のあるcommandについては, commandを実行しても何のresponseも出力されない. 上手く処理されているかどうかしりたければ, ログ(後述)に頼るしかない.
各commandのarguements, response bodyについては, src/protocol.d.ts を見れば完全な情報が記載されているので参考にされたし.
実用時のcommand flow
実際にエディタ向けのpluginを作成する場合は, 下記のようなフローに従う.
- TSServerのプロセスを立ち上げる.
- oepn commandで対象の.tsファイルをTSServer上で開く.
- definitionやcompletions commandでTSSserverから情報を引っこ抜く.
- エディタ側で対象の.tsファイルを編集したら, reload or change commandで変更内容をTSServerに通達.
- 編集が完全に完了したらclose comanndでファイルを閉じる.
- (エディタ終了時等で)TSServerのプロセスを落とす.
諸注意
ファイルパス
上記の例では, 簡便のために相対パスで記載していたが, "arguments: {"file": "..."}}
については, 絶対パスで指定すること .
また, windowsで使う場合は, \\
は /
に置き換えておくこと.
tsconfig.json
open command実行時に, 対象ファイルのbaseディレクトリからrootに向かって, tsconfig.json
を探索する.
このときにtsconfig.jsonが見つかれば, compilerOptionsが適用される.
当然, geterr commandの結果に影響してくる.
自分でtsconfig.jsonをopenしても意味はないし, reloadやchangeコマンドの実行時は探索されない.
したがって, open commandの後に tsconfig.jsonを変更した場合, TSServerにtsconfigの変更を通知する術が現状存在しないため, close & openを実行するしかない.
geterr
geterr commandは結果が非同期で返ってくる.
従って, 先のdefinition.jsonをリダイレクトで食わせるような実行をしても, eventがemitされる前にprocessが落ちてしまう.
また, このcommandは1リクエストに対して, "synatxDeagnostic"(文法エラー情報)と"semanticsDegnostic"(セマンティックエラー情報)の二つのイベントを発火させるため, plugin側で両方のイベント(標準出力)を監視する必要がある.
困った時は
1.5.0-alphaがリリースされる前からtsuquyomiの開発を始めていたというのもあるが, APIが思う通りの結果を返してくれず、うんうん悩む時間も多かった.
困ったらログとデバッグですよねー.
ログ
process.env.TSS_LOG
でTSServerにログを吐かせることが出来る.
export TSS_LOG="-file `pwd`/tsserver.log -level verbose"
-file
で出力先ファイルの指定, -level
で出力レベルの指定が行える.
デバッグ
ログでも追えないときはnode-inspectorを使ってブレークポイント仕掛けていけば何とかなる.
まとめ
- TSServer使ってイカしたIDEプラグイン作ろうぜ. VS使いに見せつけてやろうぜ