# 機能設計書 31-LSPサーバー管理

## 概要

本ドキュメントは、OpenCodeにおけるLanguage Server Protocol (LSP) サーバーの管理機能について記載する。この機能は複数言語のLSPサーバーを自動検出・起動・管理し、コード解析機能を提供する基盤となる。

### 本機能の処理概要

**業務上の目的・背景**：AIアシスタントがコードベースを深く理解し、正確なコード編集・ナビゲーション・診断を行うためには、各プログラミング言語固有の構文解析・型チェック・シンボル解決が必要である。LSPサーバー管理機能は、30以上のプログラミング言語に対応したLSPサーバーを自動的に起動・接続・管理することで、言語横断的なコード解析基盤を提供する。

**機能の利用シーン**：
- 開発者がプロジェクトを開いた際に、プロジェクト内のファイル拡張子に基づいて適切なLSPサーバーが自動起動される
- TypeScript、Python、Go、Rust等の各言語ファイルを編集する際に、対応するLSPサーバーが診断・補完情報を提供する
- 複数のサブプロジェクト（モノレポ構成）で、各サブプロジェクトのルートに応じた独立したLSPクライアントが管理される

**主要な処理内容**：
1. 設定ファイルからLSP設定の読み込みとカスタムLSPサーバーのマージ
2. ファイル拡張子に基づくLSPサーバーの自動選択
3. プロジェクトルート検出ロジックによる適切なワーキングディレクトリの決定
4. LSPサーバープロセスの起動とJSON-RPC接続の確立
5. LSPクライアントの初期化とcapabilities交渉
6. 複数LSPクライアントのライフサイクル管理（起動・監視・シャットダウン）
7. LSPサーバーのオンデマンド自動ダウンロード・インストール

**関連システム・外部連携**：
- 各言語のLSPサーバー実装（typescript-language-server、gopls、pyright等）
- GitHub Releases API（LSPサーバーの自動ダウンロード用）
- 設定ファイル（opencode.json）によるカスタムLSP設定

**権限による制御**：LSP機能は設定ファイルで無効化可能（`lsp: false`）。また、個別のLSPサーバーも`disabled: true`で無効化できる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 該当なし | - | - | LSPサーバー管理はバックエンド機能のため直接の画面表示はない |

## 機能種別

バックグラウンドサービス / プロセス管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| file | string | Yes | 解析対象ファイルの絶対パス | ファイルが存在すること |
| lsp (設定) | boolean \| object | No | LSP機能の有効/無効設定 | `false`で全LSP無効化 |
| lsp.{name} (設定) | object | No | 個別LSPサーバー設定 | command配列が必須 |

### 入力データソース

- `opencode.json` / `opencode.jsonc`: LSP設定の読み込み元
- ファイルシステム: 対象ファイルの拡張子とプロジェクトルートの検出
- 環境変数: LSPサーバーバイナリのパス検出

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| Status | LSPStatus[] | 接続中のLSPサーバーステータス一覧 |
| Status.id | string | LSPサーバーID |
| Status.name | string | LSPサーバー名 |
| Status.root | string | LSPサーバーのワーキングディレクトリ |
| Status.status | "connected" \| "error" | 接続ステータス |

### 出力先

- インメモリ状態管理: LSPクライアントインスタンスの保持
- バスイベント: `lsp.updated`イベントの発行

## 処理フロー

### 処理シーケンス

```
1. LSP状態初期化
   └─ 設定ファイルからLSP設定を読み込み
   └─ 組み込みLSPサーバー定義とマージ
   └─ 実験的フラグに基づくサーバーフィルタリング

2. ファイルアクセス時のLSPクライアント取得
   └─ ファイル拡張子から対応LSPサーバーを検索
   └─ プロジェクトルート検出関数の実行
   └─ 既存クライアントの再利用または新規起動

3. LSPサーバー起動プロセス
   └─ サーバーバイナリの存在確認
   └─ 必要に応じて自動ダウンロード・インストール
   └─ 子プロセスとしてサーバー起動
   └─ JSON-RPC接続の確立

4. LSPクライアント初期化
   └─ initializeリクエストの送信
   └─ capabilities交渉
   └─ initializedノーティフィケーションの送信
   └─ 初期設定の同期

5. ライフサイクル管理
   └─ 接続エラー時のリトライ制御（broken setで管理）
   └─ セッション終了時のシャットダウン処理
```

### フローチャート

```mermaid
flowchart TD
    A[ファイルアクセス] --> B{LSP設定有効?}
    B -->|No| Z[処理終了]
    B -->|Yes| C[ファイル拡張子取得]
    C --> D{対応LSPサーバーあり?}
    D -->|No| Z
    D -->|Yes| E[プロジェクトルート検出]
    E --> F{既存クライアントあり?}
    F -->|Yes| G[既存クライアント返却]
    F -->|No| H{起動中?}
    H -->|Yes| I[起動完了待ち]
    I --> G
    H -->|No| J[LSPサーバー起動]
    J --> K{起動成功?}
    K -->|No| L[brokenセットに追加]
    L --> Z
    K -->|Yes| M[LSPクライアント作成]
    M --> N[initialize送信]
    N --> O[クライアント登録]
    O --> P[lsp.updatedイベント発行]
    P --> G
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-31-01 | 拡張子マッチング | ファイル拡張子がLSPサーバーの対応拡張子リストに含まれる場合のみ起動対象とする | 常時 |
| BR-31-02 | ルート検出優先順位 | deno.json/deno.jsoncが存在する場合はDenoサーバーを優先し、TypeScriptサーバーを除外する | TypeScript/JavaScript ファイル |
| BR-31-03 | 起動失敗時の再試行抑制 | 一度起動に失敗したサーバー/ルート組み合わせは同一セッション内で再試行しない | 起動失敗後 |
| BR-31-04 | 実験的LSPフラグ | OPENCODE_EXPERIMENTAL_LSP_TY有効時はpyrightを無効化しtyを有効化する | Python ファイル |

### 計算ロジック

特になし

## データベース操作仕様

### 操作別データベース影響一覧

本機能はデータベースを使用しない。インメモリ状態管理のみ。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| LSPInitializeError | 初期化エラー | LSPサーバーが初期化応答を返さない | タイムアウト後にプロセス終了、brokenセットに追加 |
| SpawnError | 起動エラー | LSPサーバーバイナリが存在しない | 自動ダウンロードを試行、失敗時はログ出力 |
| ConnectionError | 接続エラー | JSON-RPC接続が切断された | クライアントをリストから削除、再起動可能 |

### リトライ仕様

- 初期化タイムアウト: 45,000ミリ秒
- 起動失敗したサーバーはセッション終了まで再試行しない（broken setで管理）

## トランザクション仕様

該当なし（データベースを使用しない）

## パフォーマンス要件

- LSPサーバー起動は非同期で行い、UIをブロックしない
- 同一サーバー/ルートの組み合わせに対する並行起動要求は単一の起動プロセスにまとめる（spawning Mapで管理）

## セキュリティ考慮事項

- LSPサーバーは設定されたワーキングディレクトリ内でのみ動作
- カスタムLSPサーバー設定では任意のコマンドが実行可能なため、設定ファイルの信頼性に依存
- 自動ダウンロード機能は`OPENCODE_DISABLE_LSP_DOWNLOAD`環境変数で無効化可能

## 備考

対応LSPサーバー一覧：
- TypeScript (typescript-language-server)
- Deno
- Python (pyright, ty)
- Go (gopls)
- Rust (rust-analyzer)
- C/C++ (clangd)
- Java (jdtls)
- Ruby (rubocop)
- Elixir (elixir-ls)
- Vue, Svelte, Astro
- その他30以上の言語

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

LSPサーバーとクライアントの型定義を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | index.ts | `packages/opencode/src/lsp/index.ts` | LSP名前空間のRange、Symbol、DocumentSymbol、Status型定義（21-160行目） |
| 1-2 | server.ts | `packages/opencode/src/lsp/server.ts` | LSPServer.Info、LSPServer.Handle インターフェース定義（22-59行目） |
| 1-3 | client.ts | `packages/opencode/src/lsp/client.ts` | LSPClient.Info型、Diagnostic型の定義（21-40行目） |

**読解のコツ**: LSPServer.Infoはサーバー定義（起動方法、対応拡張子、ルート検出関数）、LSPClient.Infoは実行中クライアントインスタンスを表す。

#### Step 2: エントリーポイントを理解する

LSPモジュールの初期化とクライアント取得の流れ。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/opencode/src/lsp/index.ts` | state関数によるLSP状態の初期化（79-144行目） |

**主要処理フロー**:
1. **79-93行目**: Instance.stateによる遅延初期化パターン
2. **95-97行目**: 組み込みLSPサーバー定義の登録
3. **99行目**: filterExperimentalServersによる実験的サーバーのフィルタリング
4. **101-126行目**: 設定ファイルからのカスタムLSPサーバーマージ

#### Step 3: LSPクライアント取得ロジック

ファイルアクセス時のクライアント取得と起動処理。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.ts | `packages/opencode/src/lsp/index.ts` | getClients関数（177-262行目） |

**主要処理フロー**:
- **179-180行目**: ファイル拡張子の抽出
- **224-225行目**: 対応サーバーと拡張子のマッチング
- **227-235行目**: 既存クライアントの再利用判定
- **237-243行目**: 起動中タスクの待機処理
- **245-258行目**: 新規サーバー起動とイベント発行

#### Step 4: LSPサーバー起動処理

各言語固有のサーバー起動実装。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | server.ts | `packages/opencode/src/lsp/server.ts` | NearestRoot関数（29-51行目） |
| 4-2 | server.ts | `packages/opencode/src/lsp/server.ts` | Typescript LSPサーバー定義（89-116行目） |
| 4-3 | server.ts | `packages/opencode/src/lsp/server.ts` | Gopls LSPサーバー定義（358-398行目） |

**主要処理フロー**:
- **29-51行目**: NearestRootパターン - 親ディレクトリを遡ってプロジェクトルートを検出
- **96-99行目**: TypeScriptサーバーのtsserverパス解決
- **373-387行目**: Goplsの自動インストール処理

#### Step 5: LSPクライアント初期化

JSON-RPC接続の確立と初期化シーケンス。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | client.ts | `packages/opencode/src/lsp/client.ts` | create関数（42-250行目） |

**主要処理フロー**:
- **46-48行目**: JSON-RPC接続の作成
- **51-62行目**: diagnosticsノーティフィケーションハンドラ
- **82-125行目**: initializeリクエストの送信とcapabilities交渉
- **127行目**: initializedノーティフィケーション
- **148-204行目**: ファイルオープン/変更通知の実装

### プログラム呼び出し階層図

```
LSP.touchFile(file)
    │
    ├─ getClients(file)
    │      │
    │      ├─ state() - LSP状態取得
    │      │
    │      ├─ server.root(file) - プロジェクトルート検出
    │      │      └─ NearestRoot() / カスタムルート関数
    │      │
    │      ├─ [既存クライアントなし] schedule()
    │      │      │
    │      │      ├─ server.spawn(root) - サーバー起動
    │      │      │      └─ spawn() (child_process)
    │      │      │
    │      │      └─ LSPClient.create()
    │      │             ├─ createMessageConnection()
    │      │             ├─ connection.sendRequest("initialize")
    │      │             └─ connection.sendNotification("initialized")
    │      │
    │      └─ Bus.publish(Event.Updated)
    │
    └─ client.notify.open({path: file})
           ├─ sendNotification("workspace/didChangeWatchedFiles")
           └─ sendNotification("textDocument/didOpen")
```

### データフロー図

```
[入力]                    [処理]                         [出力]

ファイルパス ────────▶ 拡張子抽出 ────────▶ サーバー選択
                            │
                            ▼
設定ファイル ────────▶ カスタムLSP設定 ────▶ サーバー定義マージ
                            │
                            ▼
ファイルシステム ────▶ ルート検出 ────────▶ ワーキングディレクトリ
                            │
                            ▼
                    サーバープロセス起動
                            │
                            ▼
                    JSON-RPC接続確立
                            │
                            ▼
                    クライアントインスタンス ──▶ インメモリ状態
                            │
                            ▼
                    イベント発行 ──────────▶ lsp.updated
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/opencode/src/lsp/index.ts` | ソース | LSPモジュールのメインエントリーポイント、状態管理、公開API |
| server.ts | `packages/opencode/src/lsp/server.ts` | ソース | 30以上のLSPサーバー定義、起動ロジック、自動インストール |
| client.ts | `packages/opencode/src/lsp/client.ts` | ソース | LSPクライアント実装、JSON-RPC接続、診断情報管理 |
| language.ts | `packages/opencode/src/lsp/language.ts` | ソース | ファイル拡張子と言語IDのマッピング定義 |
| lsp.ts | `packages/opencode/src/tool/lsp.ts` | ソース | LSPツール定義（エージェントからの利用インターフェース） |
| config.ts | `packages/opencode/src/config/config.ts` | ソース | LSP設定の読み込み元 |
