# プログラム設計書 21-workspace-symbol

## 1. 概要

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

このプログラムは、ワークスペース（プロジェクト）全体からシンボル（変数、関数、クラスなど）を検索するためのものです。ユーザーが入力したキーワードに基づいて、プロジェクト内のすべてのファイルから一致するシンボルを探し出し、その一覧を返します。

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

- **呼び出し元**: LSPプロトコルの `workspace/symbol` リクエストハンドラ（provider.lua）
- **主な処理**: キーワードに基づいてワークスペース全体からシンボルを検索
- **出力**: 一致したシンボルの情報（名前、種類、位置）のリスト

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

| 項目 | 内容 |
|-----|------|
| ファイルパス | `script/core/workspace-symbol.lua` |
| ファイル名 | workspace-symbol.lua |
| 種別 | ソースコード |
| 使用言語 | Lua |
| 総行数 | 161行 |
| 作成目的 | ワークスペース全体のシンボル検索機能を提供する |

## 3. 用語解説

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

| 用語 | 読み方 | 意味・説明 |
|-----|--------|----------|
| Symbol | シンボル | プログラム内の変数、関数、クラスなどの識別子のこと |
| Workspace | ワークスペース | 開いているプロジェクト全体のこと |
| AST | エーエスティー | Abstract Syntax Tree（抽象構文木）の略。ソースコードを構造化したデータ |
| URI | ユーアールアイ | ファイルの場所を示す識別子（例: file:///path/to/file.lua） |
| Global | グローバル | プログラム全体からアクセスできる変数や関数のこと |
| Field | フィールド | テーブルやオブジェクトのメンバー変数のこと |
| Method | メソッド | テーブルやオブジェクトに属する関数のこと |

### 言語固有の概念

- **require**: Luaで他のモジュールを読み込む関数です。他の言語の `import` に相当します。
- **local**: 変数のスコープをローカル（その関数やブロック内のみ）に限定するキーワードです。
- **function**: 関数を定義するキーワードです。`local function` とすることでローカル関数になります。
- **return function(...)**: 無名関数（名前のない関数）を返す記法です。このファイルの最後で使われています。

## 4. 依存関係

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

```
workspace-symbol.lua
    │
    ├─ require 'files' ........... ファイル管理モジュール
    │
    ├─ require 'parser.guide' .... AST操作ユーティリティ
    │
    ├─ require 'core.matchkey' ... キーマッチング関数
    │
    ├─ require 'proto.define' .... LSPプロトコル定義（シンボル種類等）
    │
    └─ require 'vm' .............. 仮想マシン（型推論・シンボル管理）
```

| 参照ファイル | パス | 用途 |
|------------|------|------|
| files | `script/files.lua` | ワークスペース内のファイル情報を取得 |
| guide | `script/parser/guide.lua` | ASTの走査・操作 |
| matchkey | `script/core/matchkey.lua` | キーワードマッチング判定 |
| define | `script/proto/define.lua` | シンボル種類の定義値 |
| vm | `script/vm/` | グローバル変数・クラス情報の取得 |

## 5. データ構造詳細

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

#### 検索結果オブジェクト（7-44行目、82-86行目等）

| 項目名 | 型 | 用途・説明 |
|-------|--------|----------|
| name | string | シンボルの名前 |
| skind | number | シンボルの種類（LSP SymbolKind） |
| ckind | number | 補完アイテムの種類（LSP CompletionItemKind） |
| source | table | シンボルの元となるASTノード |

**解説**: 検索結果として返される各シンボルの情報を格納するオブジェクトです。シンボルの名前、種類（変数、フィールド、メソッドなど）、および元のソースコード位置を含みます。

## 6. 処理詳細解説

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

### 6.1 処理一覧

| No | 処理名 | 行番号 | 概要 |
|----|-------|--------|------|
| 1 | buildSource | 7-44 | 個々のソースノードから検索結果を構築 |
| 2 | searchFile | 46-55 | 単一ファイル内のシンボルを検索 |
| 3 | searchGlobalAndClass | 60-91 | グローバル変数とクラスを検索 |
| 4 | searchClassField | 96-134 | クラスのフィールドを検索 |
| 5 | searchWords | 139-146 | ワークスペース全体を検索 |
| 6 | メイン関数 | 151-161 | 検索を統合して結果を返す |

### 6.2 buildSource の詳細（7-44行目）

**この処理は何をするものか**: 1つのASTノード（ソースコードの構文要素）を調べ、キーワードに一致するなら検索結果に追加します。

**処理の流れ**:

```
ステップ1: ソースの種類を判定
    │
    ↓
ステップ2: 名前を取得
    │
    ↓
ステップ3: キーワードとマッチするか確認
    │
    ↓
ステップ4: 一致すれば結果に追加
```

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

1. **ステップ1: ソースの種類を判定**（8-10行目）

   まず、渡されたソースノードがどのような種類かを調べます。

   ```lua
   if     source.type == 'local'
   or     source.type == 'setlocal'
   or     source.type == 'setglobal' then
   ```

   この処理では、ローカル変数、ローカル変数への代入、グローバル変数への代入を判定しています。これらは「変数」として扱われます。

2. **ステップ2: フィールドの判定**（20-31行目）

   次に、テーブルのフィールド（`obj.field` や `{field = value}` の形式）かどうかを判定します。

   - もし `setfield` または `tablefield` なら、フィールドとして扱います。
   - フィールドの名前を取得し、キーワードとマッチするか確認します。

3. **ステップ3: メソッドの判定**（32-43行目）

   最後に、メソッド（`obj:method()` の形式）かどうかを判定します。

   - もし `setmethod` なら、メソッドとして扱います。
   - メソッドの名前を取得し、キーワードとマッチするか確認します。

**この処理のポイント**:
- シンボルの種類によって異なる `SymbolKind` を設定しています（Variable, Field, Method）
- `matchKey` 関数で部分一致検索を行います

### 6.3 searchGlobalAndClass の詳細（60-91行目）

**この処理は何をするものか**: ワークスペース全体のグローバル変数とクラス定義を検索します。

**処理の流れ**:

```
ステップ1: 全グローバル変数を取得
    │
    ↓
ステップ2: 各グローバル変数の名前をキーワードと比較
    │
    ↓
ステップ3: 一致するものの定義位置を取得
    │
    ↓
ステップ4: 種類に応じて結果に追加
```

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

1. **ステップ1: 全グローバル変数を取得**（61行目）

   ```lua
   for _, globalVar in pairs(vm.getAllGlobals()) do
   ```

   `vm.getAllGlobals()` を呼び出して、ワークスペース内で定義されている全てのグローバル変数を取得します。

2. **ステップ2: 名前のマッチング**（62-63行目）

   ```lua
   local name = globalVar:getCodeName()
   if matchKey(key, name) then
   ```

   各グローバル変数の名前を取得し、検索キーワードと一致するかどうかを確認します。

3. **ステップ3: 定義位置の取得**（64-69行目）

   一致した場合、そのシンボルの定義位置（sets）を取得します。
   - もし `suri`（検索対象URI）が指定されていれば、そのファイル内の定義のみ
   - そうでなければ、全ての定義位置を取得

4. **ステップ4: 種類の判定と結果追加**（70-88行目）

   定義の種類によって適切なシンボル種類を設定します：
   - `doc.class` の場合: Class（クラス）
   - `doc.alias` の場合: Struct（構造体）
   - その他の場合: Variable（変数）

**この処理のポイント**:
- クラスやエイリアスは LuaDoc アノテーション（`---@class` など）で定義されたもの
- 同じ名前で複数の定義がある場合、全てを結果に含めます

### 6.4 searchClassField の詳細（96-134行目）

**この処理は何をするものか**: 「クラス名.フィールド名」の形式でクラスのフィールドを検索します。

**処理の流れ**:

```
ステップ1: 検索キーをクラス名とフィールド名に分割
    │
    ↓
ステップ2: 指定されたクラスを取得
    │
    ↓
ステップ3: クラスのフィールドを列挙
    │
    ↓
ステップ4: フィールド名がマッチすれば結果に追加
```

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

1. **ステップ1: キーの分割**（97-100行目）

   ```lua
   local class, inField = key:match('^(.+)%.(.-)$')
   if not class then
       return
   end
   ```

   検索キーを「.」で分割し、クラス名とフィールド名を取得します。例えば「MyClass.myField」なら、class="MyClass"、inField="myField" となります。

2. **ステップ2: クラスの取得**（101-113行目）

   指定されたクラス名でグローバルの型を検索し、その定義を取得します。

3. **ステップ3: フィールドの列挙**（115-133行目）

   `vm.getClassFields` を使用してクラスの全フィールドを取得し、各フィールドについてマッチングを行います。

**この処理のポイント**:
- 「ClassName.field」形式の検索をサポートすることで、クラスメンバーへの直接アクセスが可能

### 6.5 メイン関数の詳細（151-161行目）

**この処理は何をするものか**: 上記の検索機能を統合して、最終的な検索結果を返します。

**処理の流れ**:

```
ステップ1: 結果配列を初期化
    │
    ↓
ステップ2: グローバルとクラスを検索
    │
    ↓
ステップ3: クラスフィールドを検索
    │
    ↓
ステップ4: （オプション）ワード検索を実行
    │
    ↓
ステップ5: 結果を返す
```

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

1. **ステップ1-3: 各種検索の実行**（152-155行目）

   ```lua
   local results = {}
   searchGlobalAndClass(key, suri, results)
   searchClassField(key, suri, results)
   ```

   まず空の結果配列を作成し、グローバル/クラス検索とクラスフィールド検索を実行します。

2. **ステップ4: ワード検索**（156-158行目）

   ```lua
   if includeWords then
       searchWords(key, suri, results)
   end
   ```

   もし `includeWords` パラメータが true なら、ファイル内の全シンボル（ローカル変数など）も検索します。この検索は結果が1000件を超えると停止します（142-143行目）。

**この処理のポイント**:
- `includeWords` を false にすることで、高速な検索が可能（グローバルのみ）
- 結果数に上限を設けることで、大規模プロジェクトでのパフォーマンスを確保

## 7. 処理フロー図

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

```mermaid
flowchart TD
    A[検索開始] --> B[結果配列を初期化]
    B --> C[グローバル/クラス検索]
    C --> D[クラスフィールド検索]
    D --> E{includeWords?}
    E -->|Yes| F[ワード検索]
    E -->|No| G[結果を返す]
    F --> H{結果が1000件超?}
    H -->|Yes| G
    H -->|No| I[次のファイルを検索]
    I --> H
```

**図の説明**:
- **検索開始**: `workspace/symbol` リクエストを受信
- **グローバル/クラス検索**: まずグローバル変数とクラス定義を検索
- **クラスフィールド検索**: 「ClassName.field」形式の検索
- **ワード検索**: オプションでローカル変数なども検索
- **結果を返す**: 検索結果をLSPクライアントに返却

## 8. 外部連携

### 呼び出し関係図

```
[呼び出し元]                    [このプログラム]                    [呼び出し先]
                                     │
provider.lua ──────────────▶ workspace-symbol.lua ──────────────▶ vm.getAllGlobals()
  (workspace/symbol)                 │                               (グローバル一覧)
                                     │
                                     └──────────────▶ files.eachFile()
                                                        (ファイル列挙)
```

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

| プログラム名 | 呼び出し契機 | 渡されるデータ |
|------------|------------|--------------|
| provider.lua | ユーザーがシンボル検索を実行（Ctrl+T等） | 検索キーワード（key）、対象URI（suri）、ワード含む（includeWords） |

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

| プログラム名 | 呼び出し目的 | 渡すデータ | 受け取るデータ |
|------------|------------|----------|--------------|
| vm.getAllGlobals | 全グローバル変数の取得 | なし | グローバル変数のリスト |
| vm.getGlobal | 特定の型の取得 | 型名 | 型情報 |
| vm.getClassFields | クラスのフィールド取得 | URI、クラス情報 | フィールドのリスト |
| files.getState | ファイルのAST取得 | URI | AST情報 |
| files.eachFile | ファイルの列挙 | 対象URI | ファイルURIのイテレータ |

## 9. エラー処理

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

### エラー一覧

| エラー種別 | 発生条件 | 対処内容 | 該当行 |
|----------|---------|---------|--------|
| ASTなし | ファイルが読み込まれていない | 処理をスキップ | 48-50 |
| クラスなし | 指定されたクラスが存在しない | 処理を終了 | 102-104 |
| 定義なし | クラスの定義が見つからない | 処理を終了 | 111-113 |

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

このプログラムは例外を投げず、エラー状態の場合は単に空の結果を返すか、その項目をスキップします。これにより、エディタが異常終了することなく、ユーザーには「結果が見つからない」という形で通知されます。

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

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

### 推奨する読み方

1. **まずメイン関数（151-161行目）を見る**: 全体の処理の流れが分かります
2. **次に searchGlobalAndClass（60-91行目）を見る**: 最も重要な検索ロジックです
3. **最後に buildSource（7-44行目）を見る**: 個々のシンボルの判定方法が分かります

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

- `return function (key, suri, includeWords)` という記法は、このファイル自体が関数を返すことを意味します。`require` した側は返された関数を呼び出して使います。
- `results[#results+1] = ...` は Lua でテーブルの末尾に要素を追加する一般的なイディオムです。`#results` はテーブルの長さを返します。
- `pairs` と `ipairs` の違いに注意：`pairs` は全てのキーを、`ipairs` は連番のインデックスのみを走査します。

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

| ファイル | パス | 関連性 |
|---------|------|--------|
| document-symbol.lua | `script/core/document-symbol.lua` | 単一ファイル版のシンボル検索。処理の違いを比較できます |
| vm.lua | `script/vm/` | グローバル変数やクラスの管理方法が分かります |
| matchkey.lua | `script/core/matchkey.lua` | 検索のマッチングアルゴリズムが分かります |

## 11. 備考

- このプログラムは LSP（Language Server Protocol）の `workspace/symbol` リクエストに対応しています
- 検索結果の上限（1000件）は `searchWords` 関数内でハードコードされています
- `includeWords` パラメータを使い分けることで、検索速度と網羅性のトレードオフを調整できます
