# 機能設計書 7-ドキュメントの変更 (textDocument/didChange)

## 概要

本ドキュメントは、lua-language-serverにおけるドキュメント変更通知の処理を記載したものである。エディタでファイル内容が変更された際に送信される`textDocument/didChange`通知を受信し、ファイル内容を更新する。

### 本機能の処理概要

**業務上の目的・背景**：LSPプロトコルでは、エディタでの編集内容をリアルタイムにサーバーに同期する必要がある。`textDocument/didChange`通知により、ユーザーの入力・削除・編集操作がサーバーに伝達され、サーバーは最新のファイル内容に基づいて診断、補完、その他の言語機能を提供できる。増分同期（incremental sync）により、変更部分のみを送信することで効率的な同期が可能。

**機能の利用シーン**：ユーザーがエディタでLuaコードを入力、削除、編集した際にリアルタイムで呼び出される。キーストロークごと、またはエディタの設定に応じたタイミングで発生する。

**主要な処理内容**：
1. didChange通知の受信とパラメータ解析
2. URIの正規化（files.getRealUri）
3. 既存テキストの取得または新規読み込み
4. テキストマージャーによる増分変更の適用
5. ファイルテキストの更新（files.setText）とバージョン更新
6. 行キャッシュの更新
7. インデント修正の適用（fix-indent）

**関連システム・外部連携**：LSPクライアント、ファイルシステム、テキストマージャー

**権限による制御**：特になし

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能は直接的な画面表示を持たない |

## 機能種別

テキスト同期 / 増分更新

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| textDocument | VersionedTextDocumentIdentifier | Yes | 変更されたドキュメントの識別子 | - |
| textDocument.uri | string | Yes | ドキュメントのURI | 有効なURI形式 |
| textDocument.version | integer | Yes | 変更後のバージョン番号 | 前回より大きい値 |
| contentChanges | TextDocumentContentChangeEvent[] | Yes | 変更内容の配列 | - |
| contentChanges[].range | Range | No | 変更範囲（増分同期時） | - |
| contentChanges[].rangeLength | integer | No | 置換されるテキスト長 | - |
| contentChanges[].text | string | Yes | 新しいテキスト | - |

### 入力データソース

LSPクライアントからのJSON-RPC通知

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| - | - | 通知のため戻り値なし |

### 出力先

なし（内部状態の更新のみ）

## 処理フロー

### 処理シーケンス

```
1. didChange通知の受信
   └─ JSON-RPCでクライアントから通知を受信

2. パラメータの解析
   └─ textDocument、contentChangesを取得

3. URIの正規化
   └─ files.getRealUri(doc.uri)でURIを正規化

4. 既存テキストの取得
   └─ files.getOriginText(uri)で現在のテキストを取得
   └─ なければutil.loadFile()でファイルから読み込み

5. テキストの差分適用
   └─ tm(text, rows, changes)でテキストマージ
   └─ 行情報も同時に更新

6. ファイルテキストの更新
   └─ files.setText(uri, text, true, callback)
   └─ callbackでバージョン番号を設定

7. 行キャッシュの更新
   └─ files.setCachedRows(uri, rows)

8. インデント修正
   └─ fixIndent(uri, changes)を適用
```

### フローチャート

```mermaid
flowchart TD
    A[didChange通知受信] --> B[URI正規化]
    B --> C[既存テキスト取得]
    C --> D{テキスト存在?}
    D -->|Yes| E[テキストマージ実行]
    D -->|No| F[ファイルから読み込み]
    F --> G[files.setText実行]
    G --> H[fixIndent適用]
    H --> I[処理完了]
    E --> J[files.setText実行]
    J --> K[行キャッシュ更新]
    K --> L[fixIndent適用]
    L --> I
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | 増分同期 | changeモード2（増分）でテキストを同期 | capability設定 |
| BR-002 | テキストマージ | 差分情報をもとに既存テキストに変更を適用 | 常時 |
| BR-003 | バージョン管理 | 変更ごとにバージョン番号を更新 | 常時 |
| BR-004 | フォールバック | テキストが存在しない場合はファイルから読み込み | 常時 |
| BR-005 | インデント修正 | 変更後にインデント修正を適用 | 常時 |

### 計算ロジック

テキストマージの計算は`text-merger.lua`モジュールで実行。範囲指定された変更を順番に適用する。

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

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

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

### テーブル別操作詳細

#### fileMap（インメモリ）

| 操作 | 項目（キー/値） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | file.text | マージ後のテキスト | files.setTextで更新 |
| UPDATE | file.version | 新しいバージョン番号 | callbackで更新 |
| UPDATE | file.rows | 行情報キャッシュ | files.setCachedRowsで更新 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | テキスト不存在 | ファイルがまだ読み込まれていない | ファイルから読み込んで処理継続 |
| - | 範囲外アクセス | 変更範囲がテキストサイズを超える | エラーログ出力、処理継続 |

### リトライ仕様

エラー時のリトライは行わない

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

トランザクション管理なし（インメモリ操作のみ）

## パフォーマンス要件

- 変更通知は即座に処理すること（キー入力のレスポンスに影響）
- 増分同期により大きなファイルでも効率的に処理

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

特になし

## 備考

- capability設定で`textDocumentSync.change = 2`（増分同期）を指定
- 非同期処理（async）として実装されている

---

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

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

### 推奨読解順序

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

テキスト変更イベントの構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | text-merger.lua | `script/text-merger.lua` | テキストマージの仕組みと行情報の管理 |

**読解のコツ**: LSPの増分同期では、変更範囲（range）と新しいテキスト（text）が送られてくる。rangeは開始位置と終了位置を行・文字で指定する。

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

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

**主要処理フロー**:
1. **299-322行目**: `textDocument/didChange`ハンドラの定義
2. **302行目**: `require 'core.fix-indent'`でインデント修正モジュールを読み込み
3. **303-305行目**: パラメータ取得とURI正規化
4. **306行目**: `files.getOriginText(uri)`で現在のテキストを取得
5. **307-311行目**: テキストがない場合のフォールバック処理
6. **313行目**: `files.getCachedRows(uri)`で行キャッシュを取得
7. **314行目**: `tm(text, rows, changes)`でテキストマージ
8. **315-317行目**: `files.setText`でテキスト更新
9. **318行目**: `files.setCachedRows(uri, rows)`で行キャッシュ更新
10. **320行目**: `fixIndent(uri, changes)`でインデント修正

#### Step 3: テキストマージ処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | text-merger.lua | `script/text-merger.lua` | テキストマージの実装 |

#### Step 4: インデント修正処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | fix-indent.lua | `script/core/fix-indent.lua` | インデント修正の実装 |

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

```
m.register 'textDocument/didChange' (provider.lua:299-322)
    |
    +-- require 'core.fix-indent' (fix-indent.lua)
    |
    +-- files.getRealUri(doc.uri) (files.lua:82-116)
    |       └─ URI正規化
    |
    +-- files.getOriginText(uri) (files.lua:401-410)
    |       └─ 現在のテキストを取得
    |
    +-- [フォールバック] util.loadFile() + files.setText()
    |
    +-- files.getCachedRows(uri) (files.lua:325-331)
    |       └─ 行キャッシュを取得
    |
    +-- tm(text, rows, changes) (text-merger.lua)
    |       └─ テキストマージ実行
    |
    +-- files.setText(uri, text, true, callback) (files.lua:229-303)
    |       +-- バージョン番号更新
    |       +-- m.onWatch('update')
    |
    +-- files.setCachedRows(uri, rows) (files.lua:333-339)
    |       └─ 行キャッシュ更新
    |
    +-- fixIndent(uri, changes) (fix-indent.lua)
            └─ インデント修正適用
```

### データフロー図

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

DidChangeParams ───────> provider.lua                      (内部状態更新)
  - textDocument           m.register 'textDocument/didChange'
    - uri                  |
    - version      ───────>+-- files.getOriginText()
  - contentChanges         |     (現在のテキスト取得)
    - range                |
    - text                 +-- tm() テキストマージャー
                           |     +-- 変更を適用
                           |     +-- 行情報を更新
                           |
                           +-- files.setText()
                           |     +-- file.text = 新テキスト
                           |     +-- file.version = 新バージョン
                           |
                           +-- files.setCachedRows()
                           |     +-- file.rows = 新行情報
                           |
                           +-- fixIndent()
                                 +-- インデント修正
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| provider.lua | `script/provider/provider.lua` | ソース | LSP通知ハンドラの登録・実行 |
| files.lua | `script/files.lua` | ソース | ファイル管理 |
| text-merger.lua | `script/text-merger.lua` | ソース | テキストマージ処理 |
| fix-indent.lua | `script/core/fix-indent.lua` | ソース | インデント修正 |
| utility.lua | `script/utility.lua` | ソース | ユーティリティ関数（loadFile等） |
| file-uri.lua | `script/file-uri.lua` | ソース | ファイルURIの変換処理 |
