# プログラム設計書 28-code-lens

## 1. 概要

### このプログラムの目的

このプログラムは、コードエディタ上に「コードレンズ」と呼ばれる追加情報を表示するためのものです。具体的には、関数の上に「この関数が何回参照されているか」という情報をインラインで表示します。

### このプログラムの役割

- **呼び出し元**: LSPプロトコルの `textDocument/codeLens` リクエストハンドラ
- **主な処理**: ファイル内の関数定義を検出し、各関数の参照数を収集する準備
- **出力**: コードレンズの位置情報リスト（詳細情報は resolve 時に取得）

## 2. ファイル基本情報

| 項目 | 内容 |
|-----|------|
| ファイルパス | `script/core/code-lens.lua` |
| ファイル名 | code-lens.lua |
| 種別 | ソースコード |
| 使用言語 | Lua |
| 総行数 | 167行 |
| 作成目的 | コードレンズ機能（参照数表示）を提供する |

## 3. 用語解説

このプログラムを理解するために必要な専門用語を解説します。

| 用語 | 読み方 | 意味・説明 |
|-----|--------|----------|
| Code Lens | コードレンズ | コード上にインラインで表示される追加情報。参照数やテスト実行ボタンなど |
| Reference | リファレンス | 参照。関数や変数が他の場所で使用されている箇所 |
| Resolve | リゾルブ | 解決。軽量な初期情報から詳細情報を取得すること |
| Metatables | メタテーブル | Luaでオブジェクト指向を実現するための仕組み |

### 言語固有の概念

- **setmetatable**: メタテーブルを設定する関数。このコードではクラスのような構造を作成するために使用
- **---@async**: 非同期関数であることを示すLuaDocアノテーション
- **await.delay()**: 処理を一時中断して他のタスクに制御を渡す非同期処理

## 4. 依存関係

### このプログラムが使用している外部ファイル

```
code-lens.lua
    │
    ├─ require 'files' ............. ファイル管理
    │
    ├─ require 'parser.guide' ...... AST操作ユーティリティ
    │
    ├─ require 'await' ............. 非同期処理
    │
    ├─ require 'proto.converter' ... LSP形式変換
    │
    ├─ require 'core.reference' .... 参照検索機能
    │
    ├─ require 'language' .......... 多言語対応
    │
    └─ require 'client' ............ クライアント設定
```

| 参照ファイル | パス | 用途 |
|------------|------|------|
| files | `script/files.lua` | ファイルのAST取得 |
| guide | `script/parser/guide.lua` | AST走査 |
| await | `script/await.lua` | 非同期処理 |
| converter | `script/proto/converter.lua` | 位置のLSP形式変換 |
| reference | `script/core/reference.lua` | 参照数の取得 |
| client | `script/client.lua` | クライアントオプション確認 |

## 5. データ構造詳細

### 主要な変数・データ項目

#### codeLens クラス（20-25行目）

| 項目名 | 型 | 用途・説明 |
|-------|--------|----------|
| state | object | パーサーの状態（AST等） |
| uri | string | 対象ファイルのURI |
| results | array | コードレンズ結果のリスト |
| resolving | table | ID から解決情報へのマッピング |
| id | integer | コードレンズのID（自動インクリメント） |

**解説**: コードレンズの収集と解決を管理するクラスです。メタテーブルを使用してオブジェクト指向的に実装されています。

#### codeLens.result オブジェクト（14-17行目の定義による）

| 項目名 | 型 | 用途・説明 |
|-------|--------|----------|
| position | integer | コードレンズの表示位置 |
| id | integer | 解決時に使用するID |

**解説**: 初期段階では位置とIDのみを返します。詳細情報は resolve 時に取得します。

#### codeLens.resolving オブジェクト（12-13行目の定義による）

| 項目名 | 型 | 用途・説明 |
|-------|--------|----------|
| mode | string | 解決モード（現在は 'reference' のみ） |
| source | object | 対象のASTノード |

**解説**: resolve 時に必要な情報を保持します。

## 6. 処理詳細解説

このプログラムの処理内容をステップバイステップで説明します。

### 6.1 処理一覧

| No | 処理名 | 行番号 | 概要 |
|----|-------|--------|------|
| 1 | mt:init | 28-40 | コードレンズオブジェクトの初期化 |
| 2 | mt:addResult | 44-51 | 結果を追加 |
| 3 | mt:resolve | 55-64 | ID から詳細情報を取得 |
| 4 | mt:collectReferences | 67-84 | 関数の参照情報を収集 |
| 5 | mt:resolveReference | 89-121 | 参照数を表示するコマンドを生成 |
| 6 | getCodeLens | 126-145 | コードレンズのエントリポイント |
| 7 | resolve | 150-161 | 解決のエントリポイント |

### 6.2 mt:init の詳細（28-40行目）

**この処理は何をするものか**: コードレンズオブジェクトを初期化します。

**ステップバイステップ解説**:

```lua
function mt:init(uri)
    self.state     = files.getState(uri)
    if not self.state then
        return false
    end
    self.uri       = uri
    self.results   = {}
    self.resolving = {}
    return true
end
```

1. ファイルのパーサー状態（AST）を取得
2. 取得できなければ false を返す
3. 各種プロパティを初期化して true を返す

### 6.3 mt:collectReferences の詳細（67-84行目）

**この処理は何をするものか**: ファイル内の全関数定義を検索し、各関数にコードレンズを追加します。

**処理の流れ**:

```
ステップ1: 処理を一時中断（await.delay）
    │
    ↓
ステップ2: 全ての関数定義を列挙
    │
    ↓
ステップ3: 各関数について
    │   └─ 代入文か return文か確認
    │   └─ コードレンズを追加
    ↓
ステップ4: 完了
```

**ステップバイステップ解説**:

1. **ステップ1: 非同期処理**（68行目）

   ```lua
   await.delay()
   ```

   長時間の処理を防ぐため、一旦制御を他のタスクに渡します。

2. **ステップ2-3: 関数の列挙と処理**（70-83行目）

   ```lua
   guide.eachSourceType(self.state.ast, 'function', function (src)
       local parent = src.parent
       if guide.isAssign(parent) then
           src = parent
       elseif parent.type == 'return' then
       else
           return
       end
       await.delay()
       self:addResult(src.start, {
           mode   = 'reference',
           source = src,
       })
   end)
   ```

   - すべての `function` ノードを検索
   - 代入文（`local f = function() end` など）の場合は親ノードを使用
   - `return` 文の場合はそのまま
   - それ以外（無名関数など）はスキップ

**この処理のポイント**:
- 名前のある関数のみにコードレンズを表示
- 非同期処理で大きなファイルでもフリーズしない

### 6.4 mt:resolveReference の詳細（89-121行目）

**この処理は何をするものか**: 関数の参照数を取得し、表示用のコマンドを生成します。

**処理の流れ**:

```
ステップ1: 参照を検索
    │
    ↓
ステップ2: クライアントオプションを確認
    │
    ↓（codeLensViewReferences が有効）
ステップ3: 参照位置のリストを作成
    │
    ↓
ステップ4: showReferences コマンドを生成
```

**ステップバイステップ解説**:

1. **ステップ1: 参照の検索**（90-91行目）

   ```lua
   local refs = getRef(self.uri, source.finish, false)
   local count = refs and #refs or 0
   ```

   `core.reference` モジュールを使用して、関数の参照箇所を取得します。

2. **ステップ2-4: コマンドの生成**（92-120行目）

   ```lua
   if client.getOption('codeLensViewReferences') then
       -- 参照位置のリストを作成
       local locations = {}
       for _, ref in ipairs(refs or {}) do
           -- ...位置を追加...
       end
       -- showReferences コマンドを生成
       local command = conv.command(
           lang.script('COMMAND_REFERENCE_COUNT', count),
           'lua.showReferences',
           { ... }
       )
       return command
   else
       -- 単純なカウント表示のみ
       local command = conv.command(
           lang.script('COMMAND_REFERENCE_COUNT', count),
           '',
           {}
       )
       return command
   end
   ```

   - `codeLensViewReferences` オプションが有効なら、クリックで参照一覧を表示
   - 無効なら、参照数の表示のみ

**この処理のポイント**:
- `COMMAND_REFERENCE_COUNT` で「N references」のようなテキストを生成
- クライアントオプションによって動作を切り替え

### 6.5 getCodeLens の詳細（126-145行目）

**この処理は何をするものか**: コードレンズのメインエントリポイントです。

**ステップバイステップ解説**:

```lua
local function getCodeLens(uri)
    local state = files.getState(uri)
    if not state then
        return nil
    end
    local codeLens = setmetatable({}, mt)
    local suc = codeLens:init(uri)
    if not suc then
        return nil
    end
    state._codeLens = codeLens  -- 後で resolve 時に使用

    codeLens:collectReferences()

    if #codeLens.results == 0 then
        return nil
    end

    return codeLens.results
end
```

1. パーサー状態を取得
2. コードレンズオブジェクトを作成・初期化
3. 状態に保存（resolve 時に必要）
4. 参照を収集
5. 結果を返す

**この処理のポイント**:
- `state._codeLens = codeLens` で状態にオブジェクトを保存
- resolve 時にこのオブジェクトを再利用

## 7. 処理フロー図

プログラム全体の処理の流れを図で示します。

```mermaid
flowchart TD
    A[codeLens リクエスト] --> B[パーサー状態取得]
    B --> C{状態存在?}
    C -->|No| Z[nullを返す]
    C -->|Yes| D[codeLensオブジェクト作成]
    D --> E[初期化]
    E --> F{初期化成功?}
    F -->|No| Z
    F -->|Yes| G[状態に保存]
    G --> H[関数を列挙]
    H --> I[各関数にコードレンズ追加]
    I --> J{結果あり?}
    J -->|No| Z
    J -->|Yes| K[結果を返す]
```

**図の説明**:
- **codeLens リクエスト**: エディタからの textDocument/codeLens リクエスト
- **関数を列挙**: ファイル内の全関数定義を検索
- **結果を返す**: 位置とIDのリストを返す（詳細は resolve で取得）

## 8. 外部連携

### 呼び出し関係図

```
[呼び出し元]                    [このプログラム]                    [呼び出し先]
                                     │
provider.lua ──────────────▶ code-lens.lua ────────────────▶ guide.eachSourceType()
  (textDocument/codeLens)            │  (getCodeLens)            (関数列挙)
                                     │
provider.lua ──────────────▶ code-lens.lua ────────────────▶ reference.lua
  (codeLens/resolve)                 │  (resolve)                (参照検索)
```

### 呼び出し元（このプログラムを呼び出すもの）

| プログラム名 | 呼び出し契機 | 渡されるデータ |
|------------|------------|--------------|
| provider.lua | ファイルを開いた時、編集時 | URI |
| provider.lua | コードレンズをホバーした時 | URI, ID |

### 呼び出し先（このプログラムが呼び出すもの）

| プログラム名 | 呼び出し目的 | 渡すデータ | 受け取るデータ |
|------------|------------|----------|--------------|
| files.getState | AST取得 | URI | パーサー状態 |
| guide.eachSourceType | 関数検索 | AST, 'function', コールバック | なし |
| reference.lua | 参照検索 | URI, 位置 | 参照リスト |

## 9. エラー処理

このプログラムで発生する可能性があるエラーと、その対処方法を説明します。

### エラー一覧

| エラー種別 | 発生条件 | 対処内容 | 該当行 |
|----------|---------|---------|--------|
| 状態なし | ファイルが読み込まれていない | null を返す | 127-129 |
| 初期化失敗 | init が false を返す | null を返す | 133-135 |
| 結果なし | 関数定義がない | null を返す | 140-142 |

### エラー発生時の動作

このプログラムは例外を投げません。エラー状態の場合は `nil` を返し、コードレンズは表示されません。

## 10. このプログラムを読むときのポイント

このプログラムを理解するためのアドバイスです。

### 推奨する読み方

1. **まず getCodeLens を見る**: 全体の流れを把握
2. **次に collectReferences を見る**: どの関数にコードレンズを付けるか
3. **最後に resolveReference を見る**: 詳細情報の取得方法

### 注意すべきポイント

- `setmetatable({}, mt)` でクラスのインスタンスを作成しています
- `state._codeLens` でパーサー状態にオブジェクトを保存し、resolve 時に再利用
- `await.delay()` で非同期処理を行い、UIのフリーズを防いでいます

### 関連して読むべきファイル

| ファイル | パス | 関連性 |
|---------|------|--------|
| reference.lua | `script/core/reference.lua` | 参照検索の実装 |
| provider.lua | `script/provider/provider.lua` | LSPリクエストのハンドラ |

## 11. 備考

- コードレンズは2段階（codeLens → resolve）で処理されます
- 最初の codeLens リクエストでは軽量な位置情報のみを返し、ホバー時に resolve で詳細を取得することで、パフォーマンスを向上させています
- `codeLensViewReferences` オプションでクリック時の動作を制御できます
