# 機能設計書 41-入力時フォーマット (textDocument/onTypeFormatting)

## 概要

本ドキュメントは、Lua Language Serverにおける入力時フォーマット機能（textDocument/onTypeFormatting）の設計仕様を記述する。この機能は、ユーザーが特定の文字（主に改行）を入力した際に、自動的にコードの整形を行う。

### 本機能の処理概要

入力時フォーマット機能は、ユーザーがLuaコードを編集中にリアルタイムでコード整形を適用し、コーディング効率と可読性を向上させる。

**業務上の目的・背景**：開発者がLuaコードを記述する際、適切なインデントや改行位置を維持することは可読性とメンテナンス性に直結する。手動でインデントを調整する手間を省き、一貫したコーディングスタイルを自動的に適用することで、開発効率を向上させる。特に、関数定義やブロック構造の後に改行した際の自動インデント調整は、スムーズなコーディング体験を提供する。

**機能の利用シーン**：
- 関数やif文、forループなどのブロック構造を記述した後に改行した際
- テーブル定義内で改行した際
- 複数行の文字列リテラルを記述した際
- 一般的なコード記述中に改行キーを押した際

**主要な処理内容**：
1. クライアントからonTypeFormattingリクエストを受信
2. 対象ドキュメントの状態を取得
3. トリガー文字（改行）の検証
4. code_formatモジュールを使用してフォーマット処理を実行
5. 設定に基づいたフォーマットオプションを適用
6. テキスト編集結果をクライアントに返却

**関連システム・外部連携**：
- code_formatモジュール（外部Luaモジュール）との連携
- LSPクライアント（VSCode等）とのJSON-RPC通信

**権限による制御**：特に権限による制御は行われない。設定（Lua.typeFormat.config）により動作をカスタマイズ可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 24 | 入力時フォーマット | 主機能 | タイピング時の自動整形を実行する主処理 |

## 機能種別

コードフォーマット処理 / エディタ補助機能

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| textDocument.uri | string | Yes | 対象ドキュメントのURI | 有効なファイルURIであること |
| position | Position | Yes | カーソル位置（行・列） | 有効な位置情報であること |
| ch | string | Yes | トリガー文字 | '\n'（改行）のみ対応 |
| options | FormattingOptions | Yes | フォーマットオプション | insertSpaces, tabSizeを含む |

### 入力データソース

- LSPクライアントからのJSON-RPCリクエスト
- ファイルシステム上のLuaソースコード

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| TextEdit[] | array | テキスト編集の配列 |
| TextEdit.range | Range | 編集対象の範囲 |
| TextEdit.newText | string | 置換するテキスト |

### 出力先

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

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ textDocument/onTypeFormattingメッセージを受信

2. ドキュメント状態取得
   └─ files.getState(uri)でパース済み状態を取得

3. トリガー文字検証
   └─ ch == '\n'でなければnilを返却

4. 位置変換
   └─ LSP位置を内部位置に変換

5. フォーマット実行
   └─ code_format.type_format()を呼び出し

6. 結果変換
   └─ 内部形式からLSP形式に変換

7. レスポンス返却
   └─ TextEdit配列を返却
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{ワークスペース準備完了?}
    B -->|No| C[待機]
    C --> B
    B -->|Yes| D{ドキュメント状態取得}
    D -->|失敗| E[null返却]
    D -->|成功| F{トリガー文字が'\n'?}
    F -->|No| E
    F -->|Yes| G[type-formatting.lua呼び出し]
    G --> H{code_formatモジュール読み込み}
    H -->|失敗| E
    H -->|成功| I[type_format実行]
    I --> J{フォーマット成功?}
    J -->|No| E
    J -->|Yes| K[TextEdit配列生成]
    K --> L[タブ文字変換]
    L --> M[レスポンス返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-41-01 | トリガー文字制限 | 改行文字('\n')のみがフォーマットをトリガーする | 常時 |
| BR-41-02 | テスト環境無効化 | TEST変数がtrueの場合はnilを返却 | TEST==true |
| BR-41-03 | タブ/スペース変換 | insertSpaces設定に基づきタブをスペースに変換 | insertSpaces==true |

### 計算ロジック

タブ文字変換ロジック:
```lua
if params.options.insertSpaces then
    tab = (' '):rep(params.options.tabSize)
end
newText = edit.text:gsub('\t', tab)
```

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

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

本機能はデータベース操作を行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | null返却 | ドキュメント状態が取得できない | クライアントは編集を行わない |
| - | null返却 | code_formatモジュールが読み込めない | クライアントは編集を行わない |
| - | null返却 | フォーマット処理が失敗 | クライアントは編集を行わない |

### リトライ仕様

リトライは行わない。エラー時はnullを返却し、クライアント側で編集をスキップする。

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

該当なし（メモリ内処理のみ）

## パフォーマンス要件

- ユーザー入力に対するリアルタイム応答が必要
- 処理時間は100ms以内を目標とする
- abortByFileUpdate=trueにより、ファイル更新時は処理をキャンセル

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

- 外部モジュール（code_format）のpcallによる安全な読み込み
- ファイルURIの正規化による不正パス対策

## 備考

- code_formatモジュールは外部依存であり、インストールされていない環境では機能しない
- firstTriggerCharacterとして'\n'が設定されている

---

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

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

### 推奨読解順序

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

まず、LSPプロトコルで使用されるデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | converter.lua | `script/proto/converter.lua` | Position/Range型の変換処理を理解する |

**読解のコツ**: LSPプロトコルの位置情報（0-indexed）と内部表現の変換に注目する。

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

LSPリクエストハンドラの登録処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | provider.lua | `script/provider/provider.lua` | onTypeFormattingの登録と処理フローを理解する |

**主要処理フロー**:
1. **1335-1341行目**: capability定義（firstTriggerCharacter: '\n'）
2. **1344-1346行目**: ワークスペース準備待機とドキュメント状態取得
3. **1353-1355行目**: core/type-formatting.lua呼び出し
4. **1359-1370行目**: タブ文字変換とレスポンス生成

#### Step 3: コアロジックを理解する

フォーマット処理の中核を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | type-formatting.lua | `script/core/type-formatting.lua` | 実際のフォーマット処理を理解する |

**主要処理フロー**:
- **4-7行目**: トリガー文字が'\n'以外の場合は早期リターン
- **8-11行目**: code_formatモジュールのpcallによる安全な読み込み
- **18-20行目**: typeFormatOptions設定の取得
- **20行目**: code_format.type_format()呼び出し
- **21-28行目**: 結果のテキスト編集情報の生成

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

```
provider.lua (m.register 'textDocument/onTypeFormatting')
    │
    ├─ workspace.awaitReady(uri)
    │      └─ workspace.lua: ワークスペース準備完了を待機
    │
    ├─ files.getState(uri)
    │      └─ files.lua: パース済みドキュメント状態を取得
    │
    ├─ converter.unpackPosition(state, params.position)
    │      └─ converter.lua: LSP位置を内部位置に変換
    │
    ├─ core/type-formatting.lua
    │      │
    │      ├─ files.getOriginText(uri)
    │      │      └─ files.lua: 元のテキストを取得
    │      │
    │      ├─ converter.packPosition(state, position)
    │      │      └─ converter.lua: 内部位置をLSP位置に変換
    │      │
    │      ├─ config.get(uri, 'Lua.typeFormat.config')
    │      │      └─ config.lua: フォーマット設定を取得
    │      │
    │      └─ code_format.type_format()
    │             └─ 外部モジュール: 実際のフォーマット処理
    │
    └─ converter.packRange(state, edit.start, edit.finish)
           └─ converter.lua: 内部範囲をLSP範囲に変換
```

### データフロー図

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

LSPリクエスト             provider.lua
  ├─ uri          ───▶    │
  ├─ position             ├─ workspace.awaitReady()
  ├─ ch ('\n')            ├─ files.getState()
  └─ options              │
                          ▼
                    type-formatting.lua
                          │
                          ├─ files.getOriginText()
                          ├─ converter.packPosition()
                          ├─ config.get()
                          └─ code_format.type_format()
                                    │
                                    ▼
                          provider.lua
                          ├─ converter.packRange()
                          └─ タブ→スペース変換
                                    │
                                    ▼                  TextEdit[]
                          LSPレスポンス ────────────▶  ├─ range
                                                       └─ newText
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| provider.lua | `script/provider/provider.lua` | ソース | LSPリクエストハンドラの登録・実行 |
| type-formatting.lua | `script/core/type-formatting.lua` | ソース | 入力時フォーマットのコアロジック |
| converter.lua | `script/proto/converter.lua` | ソース | LSPプロトコルと内部表現の変換 |
| files.lua | `script/files.lua` | ソース | ファイル管理・状態取得 |
| config.lua | `script/config/config.lua` | ソース | 設定管理 |
| workspace.lua | `script/workspace/workspace.lua` | ソース | ワークスペース管理 |
