LSP 実装メモ (Text Document Synchronization `textDocument/willSave` `textDocument/willSaveWaitUntil` 編)
週刊 LSP 第三号
前回 gopls で見つけた Issue は無事 merge された
引き続き、Text Document Synchronization 周りの仕様について書いていく。
textDocument/didOpen
通知 <- donetextDocument/didChange
通知 <- donetextDocument/willSave
通知 <- 今日ここtextDocument/willSaveWaitUntil
リクエスト <- 今日ここtextDocument/didSave
通知textDocument/didClose
通知
前回は、次回 textDocument/didClose
予定と書いたが、やっぱり順番に見ていこうと思う。
残念ながらこれまで見てきたサーバ実装に willSave
willSaveWaitUntil
を実装したものが存在しないため、まとめて仕様の紹介のみとする。
textDocument/willSave
通知
この通知は TextDocument が保存される前にクライアントから送信される。
export interface WillSaveTextDocumentParams { textDocument: TextDocumentIdentifier; reason: number; } export namespace TextDocumentSaveReason { export const Manual = 1; export const AfterDelay = 2; export const FocusOut = 3; }
TextDocumentSaveReason
は保存をトリガした原因を示す。
Manual
は手動で保存されたことを示し、AfterDelay
は定期的に実行される自動保存によることを示し、FocusOut
はエディタからフォーカスが移動したことによる自動保存を示す。
textDocument/willSaveWaitUntil
リクエスト
textDocument/willSave
のリクエスト版である。
レスポンスに TextEdit[]
を返すことで、クライアントが TextDocument を保存する前にサーバが指定した編集を加えることが出来る点で異なるのだが、その変更を正しくクライアントが適用したかは保証されない。
リクエストパラメータは textDocument/willSave
と全く同じで、レスポンスの型は以下になる。
TextEdit[] | null
TextEdit
型は以下で定義される
interface TextEdit { range: Range; newText: string; }
これはその名の通り TextDocument に適用する変更を表わすための型である。
range
の start
と end
が等しいときは newText
の挿入を意味し、newText
が空のときは range
内の文字列を削除することを示す。
TextEdit[]
は単純にこれをリストにしたものではあるが、リスト内の各 range
が被ってはならない、というプロトコル上の制約が追加される。
ただし range
の中の start
が等しいことは許容される。
何をしたいかと言うと、同じ TextDocument の同じ位置への複数回の挿入を許容したいのである。
なお textDocument/didChange
の contentChanges
フィールドも deprecated な rangeLength
を持っていることを除けばこれと同一に見えるが、TextEdit[]
はあくまでそれ全体で一つの変更を示しており、contentChanges
フィールドはリストの各要素が一つの変更を示しているという点で異なる。
// これは textDocument/didChange
でのバージョン情報の扱いと range
の被りについての制約が無いことからの推測である。
今回の textDocument/willSave
textDocument/willSaveWaitUntil
は vim-lsp にも実装されておらず、適当に他の言語のサーバをあたってみるも実装されてるやつは見当らずだったためここまで。
次回 textDocument/didSave
予定
-- 追記(2020/08/15) -- かいた