# 機能設計書 9-自動補完 (textDocument/completion)

## 概要

本ドキュメントは、lua-language-serverにおけるコード補完機能の設計を記載したものである。ユーザーがコードを入力中に`textDocument/completion`リクエストを受信し、適切な補完候補を提供する。

### 本機能の処理概要

**業務上の目的・背景**：コード補完は、開発者の生産性を大幅に向上させる最も重要なIDE機能の一つである。変数名、関数名、キーワード、モジュール名などを入力途中で補完することで、タイピング量を減らし、スペルミスを防ぎ、利用可能なAPIの発見を支援する。lua-language-serverは、Lua言語の動的な特性を考慮しつつ、型推論とLuaDocアノテーションを活用して高精度な補完を提供する。

**機能の利用シーン**：
- 変数名を入力中（ローカル変数、グローバル変数）
- テーブルのフィールドにアクセスする際（`.`や`:`の後）
- 関数呼び出しの引数を入力する際
- require文のモジュール名を入力する際
- キーワード（if、function、local等）を入力する際
- LuaDocアノテーションを記述する際

**主要な処理内容**：
1. completionリクエストの受信とパラメータ解析
2. 補完が有効かどうかの設定確認
3. ワークスペースの準備完了確認
4. カーソル位置の解析と補完コンテキストの特定
5. 補完候補の収集（ローカル変数、グローバル変数、フィールド、キーワード、モジュール等）
6. 補完候補のフィルタリングとソート
7. 補完結果の返却

**関連システム・外部連携**：LSPクライアント、型推論システム、パーサー

**権限による制御**：設定`Lua.completion.enable`で補完機能の有効/無効を制御

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 2 | コード補完 | 主機能 | 入力中に補完候補を提供する主処理 |

## 機能種別

コードインテリジェンス / 自動補完

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| textDocument | TextDocumentIdentifier | Yes | 対象ドキュメントの識別子 | - |
| textDocument.uri | string | Yes | ドキュメントのURI | 有効なURI形式 |
| position | Position | Yes | カーソル位置 | - |
| position.line | integer | Yes | 行番号（0ベース） | 非負整数 |
| position.character | integer | Yes | 文字位置（0ベース） | 非負整数 |
| context | CompletionContext | No | 補完コンテキスト | - |
| context.triggerKind | integer | No | トリガー種別（1:手動、2:文字、3:不完全） | - |
| context.triggerCharacter | string | No | トリガー文字（例: ".", ":"） | - |

### 入力データソース

LSPクライアントからのJSON-RPCリクエスト

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| isIncomplete | boolean | 補完リストが不完全かどうか |
| items | CompletionItem[] | 補完候補の配列 |
| items[].label | string | 補完候補の表示ラベル |
| items[].kind | integer | 補完候補の種類（変数、関数、キーワード等） |
| items[].detail | string | 詳細情報（型情報等） |
| items[].documentation | MarkupContent | ドキュメント（説明、使用例等） |
| items[].deprecated | boolean | 非推奨かどうか |
| items[].sortText | string | ソート用テキスト |
| items[].filterText | string | フィルタリング用テキスト |
| items[].insertText | string | 挿入されるテキスト |
| items[].insertTextFormat | integer | 挿入テキストの形式（1:プレーン、2:スニペット） |
| items[].textEdit | TextEdit | テキスト編集 |
| items[].additionalTextEdits | TextEdit[] | 追加のテキスト編集（auto-require等） |
| items[].command | Command | 補完後に実行するコマンド |
| items[].data | any | resolveで使用する追加データ |

### 出力先

LSPクライアントへのJSON-RPCレスポンス

## 処理フロー

### 処理シーケンス

```
1. completionリクエストの受信
   └─ JSON-RPCでクライアントからリクエストを受信

2. 補完設定の確認
   └─ config.get(uri, 'Lua.completion.enable')で有効性確認
   └─ 無効の場合はnilを返却

3. ワークスペース準備確認
   └─ workspace.isReady(uri)で準備完了確認
   └─ 未準備の場合はnilを返却

4. 進捗表示の開始
   └─ progress.createで"Processing completion"を表示

5. 構文状態の取得
   └─ files.getState(uri)で構文木を取得

6. トリガー文字の処理
   └─ 特定のトリガー文字（改行、{、,）では処理をスキップ

7. カーソル位置の変換
   └─ converter.unpackPosition(state, params.position)

8. 補完処理の実行
   └─ core.completion(uri, pos, triggerCharacter)

9. 補完結果の変換
   └─ 各補完候補をLSPフォーマットに変換
   └─ textEdit、additionalTextEdits、documentation等を設定

10. レスポンスの返却
    └─ {isIncomplete, items}を返却
```

### フローチャート

```mermaid
flowchart TD
    A[completionリクエスト受信] --> B{補完有効?}
    B -->|No| C[nilを返却]
    B -->|Yes| D{ワークスペース準備完了?}
    D -->|No| C
    D -->|Yes| E[進捗表示開始]
    E --> F[構文状態取得]
    F --> G{トリガー文字確認}
    G -->|スキップ| C
    G -->|処理続行| H[カーソル位置変換]
    H --> I[core.completion実行]
    I --> J{結果あり?}
    J -->|No| C
    J -->|Yes| K[結果をLSPフォーマットに変換]
    K --> L[レスポンス返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | 補完有効性確認 | Lua.completion.enableがfalseの場合は補完しない | 常時 |
| BR-002 | トリガー文字スキップ | 改行、{、,ではacceptSuggestionOnEnterがoffでない限りスキップ | 常時 |
| BR-003 | ソート順序 | sortTextで補完候補の表示順を制御 | 常時 |
| BR-004 | 遅延解決 | 時間がかかる場合はdata.idを設定してresolveで詳細取得 | 処理時間 > 0.05秒 |
| BR-005 | パフォーマンス警告 | 処理時間が0.1秒を超えた場合は警告ログを出力 | 常時 |

### 計算ロジック

補完候補のソートは以下の優先順位で行われる：
1. マッチスコア（入力文字との一致度）
2. 補完種別（ローカル変数 > グローバル変数 > フィールド > キーワード）
3. アルファベット順

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

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

本機能はデータベースを使用しない（インメモリ処理のみ）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 構文状態なし | ファイルがまだ解析されていない | nilを返却 |
| - | 補完無効 | 設定で補完が無効化されている | nilを返却 |
| - | ワークスペース未準備 | ワークスペースの初期化が完了していない | nilを返却 |

### リトライ仕様

エラー時のリトライは行わない（クライアントが再リクエスト）

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

トランザクション管理なし

## パフォーマンス要件

- 補完処理は100ms以内に完了することが望ましい
- 処理時間が0.1秒を超えた場合は警告ログを出力

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

特になし

## 備考

- 補完候補の最大数はconfig設定で制御可能
- スニペット形式の補完（関数呼び出しのパラメータ展開等）をサポート

---

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

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

### 推奨読解順序

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

補完アイテムの構造とCompletionItemKindを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | define.lua | `script/proto/define.lua` | CompletionItemKindの定義 |

**読解のコツ**: CompletionItemKindは数値で定義されており、Variable=6、Function=3、Keyword=14等。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | provider.lua | `script/provider/provider.lua` | `m.register 'textDocument/completion'`でハンドラを登録 |

**主要処理フロー**:
1. **621-730行目**: `textDocument/completion`ハンドラの定義
2. **624-627行目**: 補完有効性とワークスペース準備の確認
3. **631行目**: 進捗表示の開始
4. **636-647行目**: トリガー文字によるスキップ処理
5. **649-651行目**: カーソル位置変換とcore.completion呼び出し
6. **663-720行目**: 補完結果のLSPフォーマットへの変換

#### Step 3: 補完コア処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | completion.lua | `script/core/completion/completion.lua` | 補完処理のメインロジック |

**主要処理フロー**:
- **2379-2397行目**: `completion(uri, position, triggerCharacter)`関数
- **2357-2376行目**: `tryCompletions()`で各種補完を試行
- **346-412行目**: `checkLocal()`でローカル変数を補完
- **672-688行目**: `checkField()`でフィールドを補完
- **807-877行目**: `checkKeyWord()`でキーワードを補完

#### Step 4: 補完候補の種類別処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | keyword.lua | `script/core/completion/keyword.lua` | キーワード補完 |
| 4-2 | auto-require.lua | `script/core/completion/auto-require.lua` | 自動require補完 |
| 4-3 | postfix.lua | `script/core/completion/postfix.lua` | 後置補完 |

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

```
m.register 'textDocument/completion' (provider.lua:621-730)
    |
    +-- config.get(uri, 'Lua.completion.enable')
    |
    +-- workspace.isReady(uri)
    |
    +-- progress.create()
    |
    +-- files.getState(uri)
    |
    +-- converter.unpackPosition()
    |
    +-- core.completion(uri, pos, triggerCharacter) (completion.lua:2379-2397)
            |
            +-- tryCompletions() (completion.lua:2357-2376)
                    |
                    +-- tryLuaDoc() - LuaDocコメント内の補完
                    +-- tryComment() - コメント内の補完
                    +-- postfix() - 後置補完
                    +-- tryTable() - テーブルリテラル内の補完
                    +-- trySpecial() - 特殊ケースの補完
                    +-- tryCallArg() - 関数引数の補完
                    +-- tryArray() - 配列リテラル内の補完
                    +-- tryWord() - 通常の単語補完
                    |       +-- checkLocal() - ローカル変数
                    |       +-- checkGlobal() - グローバル変数
                    |       +-- checkField() - フィールド
                    |       +-- checkModule() - モジュール
                    |       +-- checkKeyWord() - キーワード
                    |       +-- checkCommon() - 共通単語
                    +-- tryIndex() - インデックス補完
                    +-- trySymbol() - シンボル補完
```

### データフロー図

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

CompletionParams ──────> provider.lua                      CompletionList
  - textDocument           m.register 'textDocument/completion'
    - uri                  |                                  - isIncomplete
  - position       ───────>+-- core.completion()              - items[]
    - line                 |     |                              - label
    - character            |     +-- tryCompletions()           - kind
  - context                |           |                        - detail
    - triggerKind          |           +-- checkLocal()         - documentation
    - triggerCharacter     |           +-- checkGlobal()        - insertText
                           |           +-- checkField()         - textEdit
                           |           +-- checkKeyWord()       - ...
                           |           +-- ...
                           |
                           +-- 結果をLSPフォーマットに変換
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| provider.lua | `script/provider/provider.lua` | ソース | LSPリクエストハンドラ |
| completion.lua | `script/core/completion/completion.lua` | ソース | 補完処理のメインロジック |
| keyword.lua | `script/core/completion/keyword.lua` | ソース | キーワード補完定義 |
| auto-require.lua | `script/core/completion/auto-require.lua` | ソース | 自動require補完 |
| postfix.lua | `script/core/completion/postfix.lua` | ソース | 後置補完（.if、.for等） |
| matchkey.lua | `script/core/matchkey.lua` | ソース | 補完候補のマッチング |
| define.lua | `script/proto/define.lua` | ソース | CompletionItemKind等の定義 |
| converter.lua | `script/proto/converter.lua` | ソース | 位置情報の変換 |
| vm/infer.lua | `script/vm/infer.lua` | ソース | 型推論 |
| parser/guide.lua | `script/parser/guide.lua` | ソース | 構文木操作ユーティリティ |
