VSCodeでPythonを書いてるとき、関数名をCtrl+クリックすると定義元に飛べますよね。あの機能、裏側で何が動いてるか知ってますか?
答えはLSPとAST。この2つの関係を、具体例を使ってざっくり解説します。
LSPとは
LSP(Language Server Protocol) は、エディタと言語解析サーバー間の通信プロトコルです。
MicrosoftがVSCode用に作り、今では業界標準になっています。
何が嬉しいの?
LSPが登場する前は、「エディタ × 言語」の組み合わせごとに補完機能などを実装する必要がありました。
- 10エディタ × 10言語 = 100個の実装が必要...😱
LSPがあると:
- 言語側:1つのLanguage Serverを作ればOK
- エディタ側:LSPクライアントを1つ実装すればOK
みんなハッピー!🎉
LSPが提供する機能
- コード補完(autocomplete)
- 定義へジャンプ
- ホバーで型情報表示
- リネーム(参照を一括置換)
- エラー・警告表示
- etc.
ASTとは
AST(Abstract Syntax Tree / 抽象構文木) は、ソースコードをパースして得られる木構造のデータです。
コードの構造を機械が扱いやすい形で表現したもの、と思ってください。
LSPとASTの関係
ここが本題。
ASTは「内部データ構造」、LSPは「通信プロトコル」です。つまり、別物。
ソースコード
↓ パース
AST(Language Server内部で使う)
↓ 解析
Language ServerがLSPで結果をエディタに返す
Language Serverの中でASTを使ってコードを解析し、その結果をLSPプロトコルでエディタに伝える、という流れです。
イメージとしては:
- AST = 「脳内の理解」🧠
- LSP = 「それを伝える言葉」💬
具体例:Pythonで定義ジャンプ
実際のコードで見てみましょう。
コード
def hello(): # 1行目
print("hi") # 2行目
hello() # 4行目 ← ここでCtrl+クリック
AST(Pylanceが内部で持っている木構造)
Module
├── FunctionDef "hello" ← 定義: line 1, col 0
│ └── body: Call "print"
│
└── Expr
└── Call "hello" ← 参照: line 4, col 0
Pylance(PythonのLanguage Server)はこのASTを見て、「helloという名前は1行目で定義されている」と知っています。
LSPのやり取り
4行目のhello()をCtrl+クリックしたとき、裏ではこんなJSON-RPCが飛んでいます。
① エディタ → Language Server
{
"method": "textDocument/definition",
"params": {
"textDocument": { "uri": "file:///code/main.py" },
"position": { "line": 3, "character": 0 }
}
}
「4行目(0-indexed で3)の0文字目にあるhelloの定義はどこ?」
② Language Server → エディタ
{
"result": {
"uri": "file:///code/main.py",
"range": {
"start": { "line": 0, "character": 4 },
"end": { "line": 0, "character": 9 }
}
}
}
「1行目の4〜9文字目(hello)だよ」
流れまとめ
Ctrl+クリック
↓
エディタ: LSPで「3行目0文字目の定義は?」と聞く
↓
Pylance: ASTを見てFunctionDef "hello"を発見
↓
Pylance: LSPで「0行目4文字目だよ」と返す
↓
エディタ: カーソルがピョンと飛ぶ ✨
ASTはいつ作られる?
「コード書くたびにAST作り直してるの?重くない?」と思うかもしれません。
答えは「基本的には変更のたびに走る。ただし賢く最適化されている」です。
タイミング
文字入力
↓
デバウンス(数百ms待つ)← 連打してる間は待機
↓
変更があった部分だけ再パース
↓
AST更新
毎キーストロークではなく、ちょっと手が止まったタイミングで走ります。
最適化のポイント
-
増分パース(Incremental Parsing)
全ファイル再パースではなく、変更箇所の周辺だけ更新する。Tree-sitterなどがこれを得意とする。 -
ファイル単位のキャッシュ
開いていないファイルはパース結果をキャッシュしていて、必要になるまで再パースしない。 -
遅延解析
型推論などの重い処理は、実際に必要になるまで後回しにする。
体感できる例
大きなプロジェクトを開いた直後、補完がちょっとモタつくことありませんか?
あれは裏でAST構築中だから。しばらくすると爆速になる=キャッシュができた、ということです。
まとめ
| 概念 | 役割 |
|---|---|
| AST | コードの構造を表す内部データ。Language Serverの中で使う |
| LSP | エディタとLanguage Server間の通信プロトコル |
- VSCodeの「定義へジャンプ」「補完」「リネーム」などは、裏でLSPが動いている
- Language ServerはASTを使ってコードを解析し、LSPで結果を返している
- ASTはコード変更のたびに更新されるが、増分パースやキャッシュで最適化されている
普段何気なく使っている機能の裏側を知ると、エディタがちょっと愛おしくなりますね!