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

## 概要

本ドキュメントは、serve-d LSPサーバーにおけるドキュメント変更 (textDocument/didChange) 機能の設計を記述する。テキストドキュメントの変更通知を処理し、DScanner静的解析とdfmtリントをデバウンス付きで実行する。

### 本機能の処理概要

LSPプロトコルの `textDocument/didChange` 通知を処理する。D言語ファイルの変更時にDScanner静的解析（doDscanner）を呼び出し、さらにデバウンス付きでdfmtリントを実行する。ファイルサイズに応じてデバウンス遅延を調整し、大きなファイルでは過度な解析実行を抑制する。

**業務上の目的・背景**：D言語開発者がコードを編集している最中にリアルタイムで問題を検出し、エディタ上に波線や警告として表示することで、即座にフィードバックを得られる開発体験を提供する。ただし、キー入力ごとに重い解析を実行するとパフォーマンスに影響するため、デバウンス処理で適切に制御している。

**機能の利用シーン**：エディタでD言語ファイルの内容を編集（文字入力、削除、貼り付け等）するたびに自動的に通知される。

**主要な処理内容**：
1. ドキュメントの言語判定（D言語でなければスキップ）
2. DScanner静的解析の実行（doDscanner関数、デバウンス付き）
3. dfmtリントのデバウンス付き実行（ファイルサイズに応じた遅延調整）

**関連システム・外部連携**：LSPクライアント。DScanner静的解析エンジン。dfmtフォーマッター。診断マネージャ。

**権限による制御**：特に権限制御は存在しない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 34 | DScanner リント（served/doDscanner） | 補助機能 | テキスト変更時にデバウンス付きでDScanner静的解析がトリガーされる |
| 35 | dfmt リント | 補助機能 | テキスト変更時にデバウンス付きでdfmtリントがトリガーされる |

## 機能種別

イベントハンドリング（リアルタイムリント実行）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| params | DidChangeTextDocumentParams | Yes | 変更されたテキストドキュメント情報 | LSPプロトコル仕様に準拠 |
| params.textDocument.uri | DocumentUri | Yes | ドキュメントのURI | file://形式 |
| params.textDocument.version_ | int | No | ドキュメントのバージョン番号 | - |

### 入力データソース

LSPクライアントからのJSON-RPC通知（メソッド名: "textDocument/didChange"）

## 出力仕様

### 出力データ

通知処理のため、レスポンスは返却しない。リント結果はtextDocument/publishDiagnostics通知として別途送信される。

### 出力先

診断マネージャ経由でLSPクライアントへpublishDiagnostics通知が送信される。

## 処理フロー

### 処理シーケンス

```
1. ドキュメント取得
   └─ documents[params.textDocument.uri]でドキュメントを取得
2. 言語判定
   └─ document.getLanguageId != "d"の場合は即座にreturn
3. DScanner静的解析実行
   └─ doDscanner(params)を呼び出し
      └─ 内部でデバウンス処理（200ms/1000ms: ファイルサイズ依存）
      └─ enableStaticLinting && enableLinting設定チェック
4. dfmtリントのデバウンス設定
   └─ ファイルサイズ判定: > 50KiB → 500ms遅延、それ以外 → 50ms遅延
   └─ 前回タイマーをclearTimeout(genericChangeTimeout)でクリア
   └─ setTimeout()で遅延実行を設定
5. dfmtリント実行（遅延後）
   └─ served.linters.dfmt.lint(document)を呼び出し
```

### フローチャート

```mermaid
flowchart TD
    A[didChange通知受信] --> B[ドキュメント取得]
    B --> C{言語 == D?}
    C -->|No| D[処理終了]
    C -->|Yes| E[doDscanner呼び出し]
    E --> F{ファイルサイズ > 50KiB?}
    F -->|Yes| G[dfmtデバウンス: 500ms]
    F -->|No| H[dfmtデバウンス: 50ms]
    G --> I[setTimeout dfmt.lint]
    H --> I
    I --> J[処理終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | D言語限定 | D言語ファイル以外は処理をスキップする | document.getLanguageId != "d" |
| BR-02 | dfmtデバウンス（小ファイル） | 50KiB以下のファイルは50msのデバウンス遅延でdfmtリントを実行 | document.length <= 50*1024 |
| BR-03 | dfmtデバウンス（大ファイル） | 50KiBを超えるファイルは500msのデバウンス遅延でdfmtリントを実行 | document.length > 50*1024 |
| BR-04 | DScannerデバウンス（小ファイル） | 50KiB以下のファイルは200msのデバウンス遅延でDScanner解析を実行 | document.length <= 50*1024 |
| BR-05 | DScannerデバウンス（大ファイル） | 50KiBを超えるファイルは1000msのデバウンス遅延でDScanner解析を実行 | document.length > 50*1024 |
| BR-06 | DScanner設定チェック | enableStaticLintingとenableLintingの両方がtrueの場合のみDScanner解析を実行 | doDscanner内 |

### 計算ロジック

- dfmtデバウンス遅延: `document.length > 50 * 1024 ? 500 : 50` （ミリ秒）
- DScannerデバウンス遅延: `document.length > 50 * 1024 ? 1000 : 200` （ミリ秒）

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

本機能ではデータベース操作は発生しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| なし | - | エラーケースは存在しない（リンターの例外は各リンター内で処理） | - |

### リトライ仕様

リトライは不要。次の変更通知で再度リントがトリガーされる。

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

トランザクションは存在しない。

## パフォーマンス要件

- ファイルサイズに応じたデバウンス遅延により、過度な解析実行を抑制している。
- clearTimeoutで前回の未実行タイマーをキャンセルし、最後の変更のみを処理する。
- 50KiB閾値でデバウンス遅延を切り替え（dfmt: 50ms/500ms、DScanner: 200ms/1000ms）。

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

特になし。

## 備考

- doDscanner関数（extension.d 1168-1187行目）は`@protocolNotification("served/doDscanner")`と`@protocolNotification("coded/doDscanner")`（deprecated alias）の両方でマークされており、手動トリガーとしても使用可能。
- genericChangeTimeout変数（extension.d 1147行目）はdfmtリントのデバウンスタイマーID。
- dscannerChangeTimeout変数（extension.d 1167行目）はDScanner解析のデバウンスタイマーID。
- このハンドラは他のモジュール（index.d, test_provider.d等）でも`@protocolNotification("textDocument/didChange")`として追加ハンドラが登録されている場合がある。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.d | `source/served/types.d` | Configuration.D.enableLinting（86行目）、enableStaticLinting（88行目）: リンティング設定 |
| 1-2 | types.d | `source/served/types.d` | documents（54行目）: TextDocumentManagerへのalias |

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | extension.d | `source/served/extension.d` | onDidChangeDocument関数（1148-1165行目）: @protocolNotification("textDocument/didChange")ハンドラ |

**主要処理フロー**:
1. **1151行目**: documents[uri]でドキュメント取得
2. **1152-1153行目**: D言語判定。非D言語なら即return
3. **1155行目**: doDscanner(params)呼び出し
4. **1157行目**: デバウンス遅延の計算（50KiB閾値）
5. **1158行目**: clearTimeout(genericChangeTimeout)で前回タイマーキャンセル
6. **1159-1164行目**: setTimeout()でdfmt.lint(document)を遅延実行

#### Step 3: DScanner解析処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | extension.d | `source/served/extension.d` | doDscanner関数（1168-1187行目）: DScanner解析のデバウンス制御 |

**主要処理フロー**:
- **1172-1173行目**: ドキュメント取得と言語判定
- **1175-1177行目**: enableStaticLintingとenableLinting設定チェック
- **1179行目**: デバウンス遅延計算（50KiB閾値: 200ms/1000ms）
- **1180-1186行目**: clearTimeoutとsetTimeoutでデバウンス実行

#### Step 4: dfmtリント処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | dfmt.d | `source/served/linters/dfmt.d` | lint関数（24-36行目）: dfmtリントの実装 |

**主要処理フロー**:
- **26-27行目**: enableFormatting設定チェック
- **34行目**: diagnosticmanagerのcreateDiagnosticsForでスロット2に結果を格納
- **35行目**: updateDiagnosticsでクライアントに通知

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

```
onDidChangeDocument(DidChangeTextDocumentParams)  [@protocolNotification("textDocument/didChange")]
    |
    +-- documents[uri]          ... ドキュメント取得
    +-- getLanguageId()         ... 言語判定
    |
    +-- doDscanner(params)
    |       +-- [デバウンス 200ms/1000ms]
    |       +-- served.linters.dscanner.lint(document)
    |               +-- DscannerComponent.lint(...)
    |               +-- createDiagnosticsFor!0(uri)
    |               +-- updateDiagnostics(uri)
    |
    +-- [デバウンス 50ms/500ms]
        +-- served.linters.dfmt.lint(document)
                +-- DfmtComponent.findDfmtInstructions(...)
                +-- createDiagnosticsFor!2(uri)
                +-- updateDiagnostics(uri)
```

### データフロー図

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

DidChangeTextDocumentParams ──> onDidChangeDocument()
  .textDocument.uri         ──>   documents[uri] 取得
                                  |
                                  +-- doDscanner()
                                  |     [デバウンス]
                                  |     dscanner.lint()  ──────> publishDiagnostics
                                  |                               (DScanner結果)
                                  +-- [デバウンス]
                                      dfmt.lint()        ──────> publishDiagnostics
                                                                  (dfmt結果)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| extension.d | `source/served/extension.d` | ソース | onDidChangeDocument、doDscanner関数の実装 |
| dscanner.d | `source/served/linters/dscanner.d` | ソース | DScanner静的解析リンター |
| dfmt.d | `source/served/linters/dfmt.d` | ソース | dfmtリンター |
| diagnosticmanager.d | `source/served/linters/diagnosticmanager.d` | ソース | 診断結果の管理と配信 |
| types.d | `source/served/types.d` | ソース | Configuration設定値の定義 |
| index.d | `source/served/commands/index.d` | ソース | 追加のdidChangeハンドラ（インデックス更新、71-79行目） |
