# 機能設計書 28-コードレンズ解決 (codeLens/resolve)

## 概要

本ドキュメントは、serve-d LSPサーバーにおけるコードレンズ解決機能（codeLens/resolve）の設計を記述する。コードレンズの詳細情報（インポートのコンパイル時間等）を遅延解決する機能である。

### 本機能の処理概要

**業務上の目的・背景**：コードレンズの提供時（No.27）にインポートのコンパイル時間を計測すると、全てのimport文に対して一括でDMDコンパイルを実行することになり、レスポンスが大幅に遅延する。そのため、コードレンズの詳細情報は遅延解決パターンを採用し、エディタがコードレンズを表示する直前に個別に解決する。

**機能の利用シーン**：エディタがコードレンズを画面上に表示する際、未解決のコードレンズに対して呼び出される。ユーザーがスクロールして該当行が可視範囲に入ったタイミング等で発生する。

**主要な処理内容**：
1. コードレンズのデータからタイプを判別する（現在は"importcompilecheck"のみ）
2. DMDComponentのmeasureSync APIを使用してimport文のコンパイル時間を計測する
3. 計測結果をキャッシュし、一定時間内の再計測を抑制する
4. 結果を"~Nms for importing this"形式のCommandとして返却する

**関連システム・外部連携**：DMDComponent（コンパイル時間計測）

**権限による制御**：特になし（コードレンズ提供時にenableDMDImportTimingで制御済み）。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 20 | コードレンズ（textDocument/codeLens） | 主画面 | DMDインポートタイミング情報をコードレンズとして表示する |

## 機能種別

計算処理（コンパイル時間計測と結果キャッシング）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| range | TextRange | Yes | コードレンズの表示位置 | - |
| data | JsonValue | Yes | コードレンズメタデータ（type, code, module, file） | data.typeが存在すること |
| data.type | string | Yes | レンズタイプ（"importcompilecheck"） | "importcompilecheck"であること |
| data.code | string | Yes | import文のソースコード | 空でないこと |
| data.module | string | Yes | インポートモジュール名 | 空でないこと |
| data.file | string | Yes | ファイルパス | 空でないこと |

### 入力データソース

- LSPクライアントからのcodeLens/resolveリクエストのCodeLensオブジェクト

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| range | TextRange | 元のコードレンズの位置（変更なし） |
| command | Command | 解決済みのコマンド（"~Nms for importing this" または "no noticable effect for importing this"） |

### 出力先

LSPクライアント（JSON-RPCレスポンスとして返却）

## 処理フロー

### 処理シーケンス

```
1. codeLens/resolveリクエスト受信
   └─ CodeLensオブジェクトからdataを取得
2. データのバリデーション
   └─ data.isNone または data.kind != Object の場合は例外
3. タイプの判別
   └─ data.typeが"importcompilecheck"の場合のみ処理
4. code, module, fileの抽出
   └─ 各フィールドが空の場合は例外
5. getImportCompilationTime呼び出し
   5-1. キャッシュチェック
        └─ 既存キャッシュがあり有効期限内の場合はキャッシュ値を返す
   5-2. 排他制御
        └─ importCompilationTimeRunningフラグで並列実行を防止
   5-3. DMDComponent.measureSync呼び出し
        └─ import文のコンパイル時間を計測（20回実行の中央値、500ms以下のタイムアウト）
   5-4. 結果のキャッシュ
        └─ (現在時刻, code, msecs)をキャッシュに追加
   5-5. 100msのファイバーyield
        └─ リクエストのバッチング用待機
6. Command構築
   └─ 10ms未満: "no noticable effect for importing this"
   └─ 10ms以上: "~{N}ms for importing this"
7. 結果返却
```

### フローチャート

```mermaid
flowchart TD
    A[codeLens/resolve リクエスト受信] --> B{data有効?}
    B -->|No| C[例外スロー]
    B -->|Yes| D{type == importcompilecheck?}
    D -->|No| E[Unknown lens type例外]
    D -->|Yes| F[code, module, file抽出]
    F --> G{各フィールド非空?}
    G -->|No| H[例外スロー]
    G -->|Yes| I[getImportCompilationTime呼び出し]
    I --> J{キャッシュヒット?}
    J -->|Yes| K[キャッシュ値返却]
    J -->|No| L{別の計測が実行中?}
    L -->|Yes| M[Fiber.yieldで待機]
    M --> L
    L -->|No| N[DMDComponent.measureSync呼び出し]
    N --> O{コンパイル成功?}
    O -->|No| P[Compilation failed例外]
    O -->|Yes| Q[結果キャッシュ保存]
    Q --> R[100ms待機]
    R --> S{msecs < 10?}
    S -->|Yes| T[no noticable effect]
    S -->|No| U[~Nms for importing this]
    K --> S
    T --> V[Command構築して返却]
    U --> V
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-28-01 | キャッシュ有効期限 | コンパイル時間 >= 500ms: 20分、>= 30ms: 5分、< 30ms: 2分。標準ライブラリは常にキャッシュヒット | キャッシュ参照時（117-118行目） |
| BR-28-02 | 排他制御 | importCompilationTimeRunningフラグで並列実行を防止する（ブロッキング） | 計測開始時（127-131行目） |
| BR-28-03 | 計測パラメータ | measureSyncに20回の反復と500msのタイムアウトを指定 | 計測実行時（133行目） |
| BR-28-04 | 結果丸め | 計測結果を5ms単位に丸める | 結果変換時（136行目） |
| BR-28-05 | バッチング待機 | 100msのFiber.yield()でリクエストをバッチング | 計測後（140-141行目） |
| BR-28-06 | 表示閾値 | 10ms未満は"no noticable effect"と表示 | Command構築時（83-84行目） |
| BR-28-07 | 標準ライブラリ優遇 | std.で始まるモジュールは常にキャッシュを使用 | キャッシュ判定時（118行目） |

### 計算ロジック

- コンパイル時間の丸め: `cast(int) round(ret.duration.total!"msecs" / 5.0) * 5`
- キャッシュ有効期限の判定:
  - `exist.ret >= 500` → 20分
  - `exist.ret >= 30` → 5分
  - その他 → 2分
  - `module_.startsWith("std.")` → 常にキャッシュヒット

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

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

データベース操作は行わない。インメモリキャッシュ（static CompileCache[] cache）のみ使用。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Exception | lens.data.isNone または kind != Object | "Invalid Lens Object"例外 |
| - | Exception | code, module, fileが空の場合 | "No valid code/module/file provided"例外 |
| - | Exception | DMDコンパイルが失敗した場合 | "Compilation failed"例外。catchでcommandをinitに設定 |
| - | Exception | 未知のレンズタイプの場合 | "Unknown lens type"例外 |

### リトライ仕様

リトライは不要。キャッシュ有効期限切れ時に次回リクエストで再計測される。

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

トランザクションは使用しない。

## パフォーマンス要件

- DMDComponent.measureSyncは実際のDMDプロセスを実行するため、数百ミリ秒以上かかる可能性がある
- キャッシュにより再計測を抑制（最短2分、最長20分のキャッシュ有効期限）
- importCompilationTimeRunningフラグにより並列計測を防止し、DMDプロセスの同時起動を回避
- 100msのバッチング待機により、連続するresolveリクエストの処理を効率化

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

- DMDプロセスの実行を伴うが、実行されるコードはimport文のみであり任意コード実行のリスクは低い

## 備考

- CompileCacheはstatic変数として関数内に定義されており、サーバーのライフタイム全体で保持される
- 標準ライブラリ（std.*）のインポートは変化しないため、常にキャッシュから返される
- "noticable"は"noticeable"のタイポの可能性があるが、ソースコードの現状を反映している（84行目）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | code_lens.d | `source/served/commands/code_lens.d` | CompileCache構造体（102-107行目）: at(キャッシュ時刻), code(import文), ret(計測結果ms) |

**読解のコツ**: CompileCacheはstatic構造体配列として関数スコープ内に定義されている（109行目 `static CompileCache[] cache`）。これはD言語のstatic変数で、関数呼び出し間で状態が保持される。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | code_lens.d | `source/served/commands/code_lens.d` | resolveCodeLens関数（52-95行目）がLSPプロトコルメソッドのエントリーポイント |

**主要処理フロー**:
1. **55-56行目**: data有効性チェック
2. **59-61行目**: データのパース（type取得）
3. **62行目**: switchによるタイプ分岐
4. **64行目**: "importcompilecheck"ケース
5. **67-80行目**: code, module, fileの抽出と検証
6. **82行目**: `getImportCompilationTime` 呼び出し
7. **83-84行目**: Command構築（閾値判定付き）

#### Step 3: コンパイル時間計測を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | code_lens.d | `source/served/commands/code_lens.d` | getImportCompilationTime関数（98-143行目）でDMDを使ったコンパイル時間計測とキャッシング |

**主要処理フロー**:
- **113-125行目**: キャッシュ検索とヒット判定（有効期限チェック付き）
- **127-131行目**: 排他制御（importCompilationTimeRunningフラグ）
- **133行目**: `backend.best!DMDComponent(file).measureSync(code, null, 20, 500)` 計測実行
- **136行目**: 結果の5ms単位丸め
- **137行目**: キャッシュ保存
- **140-141行目**: 100msバッチング待機

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

```
resolveCodeLens (code_lens.d:53)
    |
    +-- データバリデーション (type, code, module, file)
    |
    +-- getImportCompilationTime (code_lens.d:98)
    |       |
    |       +-- キャッシュ検索 (static CompileCache[] cache)
    |       |
    |       +-- DMDComponent.measureSync (workspaced/com/dmd.d)
    |       |       └─ DMDプロセス実行（20回、500msタイムアウト）
    |       |
    |       +-- キャッシュ保存
    |       +-- Fiber.yield (100ms バッチング待機)
    |
    +-- Command構築 ("~Nms for importing this")
```

### データフロー図

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

CodeLens (data付き) -------> resolveCodeLens -------------------> CodeLens (command付き)
  type: importcompilecheck         |
  code: import文                   v
  module: モジュール名      getImportCompilationTime
  file: ファイルパス               |
                              キャッシュチェック
                                   |
                          (ミス) DMDComponent.measureSync
                                   |
                              結果キャッシュ -> Command構築
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| code_lens.d | `source/served/commands/code_lens.d` | ソース | コードレンズ解決のメイン実装（resolveCodeLens: 52-95行目, getImportCompilationTime: 98-143行目） |
| types.d | `source/served/types.d` | ソース | LSP型定義（CodeLens, Command等） |
| dmd.d (workspaced) | `workspace-d/source/workspaced/com/dmd.d` | ソース | DMDComponent.measureSync APIの実装 |
