# 通知設計書 8-textDocument/didSave

## 概要

本ドキュメントは、LSPクライアントからserve-dサーバーへ送信される `textDocument/didSave` 通知の設計を記述する。この通知はLSP標準仕様に準拠し、テキストドキュメントが保存されたことをサーバーに通知するために使用される。

### 本通知の処理概要

`textDocument/didSave` 通知は、エディタ（クライアント）でファイルが保存された際にserve-dサーバーに送信される通知である。extension.dのonDidSaveDocumentハンドラ、index.dのreindexOnSaveハンドラ、test_provider.dのハンドラ、およびdub.dのハンドラの4箇所でハンドリングされる。保存時には変更時よりも本格的なリンティング（D-Scanner + Dub）が実行される。

**業務上の目的・背景**：ドキュメントの保存はユーザーが編集を一段落させたことを示すタイミングであり、より重いビルドベースのリンティング（Dubリント）を実行する適切な契機である。変更時のリアルタイムリンティング（D-Scanner + dfmt）に加えて、保存時にはDubビルドによるコンパイルエラー検出も行うことで、より包括的なコード品質チェックを提供する。

**通知の送信タイミング**：ユーザーがエディタ上でファイルを保存したとき（Ctrl+S等）にクライアントから送信される。

**通知の受信者**：serve-d LSPサーバーが受信する。

**通知内容の概要**：保存されたドキュメントのURI（TextDocumentIdentifier）で構成される。テキスト内容は含まれない（SaveOptions.includeText=false）。

**期待されるアクション**：サーバーがD-ScannerリンティングとDubリンティングを実行し、インデックスを更新する。結果としてpublishDiagnostics通知がクライアントに送信される。

## 通知種別

LSP標準通知（クライアント -> サーバー）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（JSON-RPC通知） |
| 優先度 | 中 |
| リトライ | 無し |

### 送信先決定ロジック

クライアントがファイルを保存した際に自動的にserve-dサーバーへ送信される。

## 通知テンプレート

### LSP通知の場合

| 項目 | 内容 |
|-----|------|
| メソッド名 | `textDocument/didSave` |
| パラメータ型 | `DidSaveTextDocumentParams` |
| 形式 | JSON-RPC 2.0 通知 |

### 本文テンプレート

```json
{
  "jsonrpc": "2.0",
  "method": "textDocument/didSave",
  "params": {
    "textDocument": {
      "uri": "file:///path/to/file.d"
    }
  }
}
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| textDocument.uri | 保存されたドキュメントのURI | クライアントのファイルシステム | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| ユーザー操作 | エディタでファイルを保存 | 常に送信 | Ctrl+S等の保存操作 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| LSP接続未確立 | サーバーが初期化されていない場合 |

## 処理フロー

### 受信フロー

```mermaid
flowchart TD
    A[textDocument/didSave通知受信] --> B[ハンドラ1: onDidSaveDocument]
    A --> C[ハンドラ2: reindexOnSave]
    B --> D{D/Dietファイルかつリンティング有効?}
    D -->|Yes| E[joinAll並列実行]
    D -->|No| F[スキップ]
    E --> G[D-Scannerリント]
    E --> H[Dubリント]
    G --> I[publishDiagnostics送信]
    H --> I
    C --> J{D言語かつインデックス有効?}
    J -->|Yes| K[reindexSaved実行]
    J -->|No| L[スキップ]
```

## データベース参照・更新仕様

該当なし（インメモリ管理）

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| IndexComponent | UPDATE | ファイルのインデックスを保存ベースで再構築 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| リンター実行エラー | D-Scanner/Dub実行失敗 | 各リンター内でハンドリング |
| インデックスエラー | IndexComponent操作失敗 | index.d内でハンドリング |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0 |
| リトライ間隔 | 該当なし |
| リトライ対象エラー | 該当なし |

## 配信設定

### レート制限

該当なし（ユーザーの保存操作に連動）

### 配信時間帯

制限なし

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

ドキュメントURIのみが送信される（テキスト内容は含まれない）。セキュリティリスクは低い。

## 備考

- SaveOptions.includeText=falseに設定されているため、保存時のテキスト内容は通知に含まれない
- onDidSaveDocumentではjoinAll()で2つのリンティング（D-Scanner、Dub）を並列実行する
- 保存時にはdscannerChangeTimeoutがクリアされ、D-Scannerの遅延実行がキャンセルされる（即座に新しいリントが実行されるため）
- D言語に加えてDiet言語ファイル（.dt）もリンティング対象
- reindexOnSaveではreindexChangeTimeoutもクリアされる

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | protocol.d | `lsp/source/served/lsp/protocol.d` | DidSaveTextDocumentParams構造体の定義 |

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | extension.d | `source/served/extension.d` | onDidSaveDocument（1316行目〜1345行目）。D/Dietファイル判定後、joinAllでD-ScannerリントとDubリントを並列実行 |

**主要処理フロー**:
1. **1319行目〜1322行目**: ワークスペースルート取得、設定取得、ドキュメント取得
2. **1324行目**: D言語またはDiet言語判定
3. **1326行目**: enableLinting判定
4. **1328行目〜1343行目**: joinAll()でD-ScannerリントとDubリントを並列実行

#### Step 3: 他のハンドラを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.d | `source/served/commands/index.d` | reindexOnSave（96行目〜114行目）。reindexSavedでインデックス更新、delayedSaveIndexで2.5秒後にインデックス保存 |

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

```
[クライアント: textDocument/didSave送信]
    |
    v
[serverbase.d: processNotify]
    |
    +-- onDidSaveDocument(params)                  [extension.d:1316]
    |       +-- joinAll(
    |       |       dscanner.lint(document),        [linters/dscanner.d]
    |       |       dub.lint(document)              [linters/dub.d]
    |       +-- )
    |       +-- clearTimeout(dscannerChangeTimeout)
    |
    +-- reindexOnSave(params)                      [index.d:96]
            +-- clearTimeout(reindexChangeTimeout)
            +-- indexer.reindexSaved(filePath, rawText)
            +-- delayedSaveIndex(indexer)
```

### データフロー図

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

DidSaveTextDocumentParams  -->  onDidSaveDocument()            -->  publishDiagnostics(D-Scanner)
  textDocument.uri         -->  dscanner.lint() + dub.lint()   -->  publishDiagnostics(Dub)
                           -->  reindexOnSave()                -->  IndexComponent更新
                           -->  delayedSaveIndex()             -->  インデックスファイル保存
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| extension.d | `source/served/extension.d` | ソース | onDidSaveDocumentハンドラ（メインリンティング実行） |
| index.d | `source/served/commands/index.d` | ソース | reindexOnSaveハンドラ（インデックス更新） |
| dscanner.d | `source/served/linters/dscanner.d` | ソース | D-Scanner静的解析リンター |
| dub.d | `source/served/linters/dub.d` | ソース | Dubビルドエラーリンター |
| diagnosticmanager.d | `source/served/linters/diagnosticmanager.d` | ソース | 診断結果の統合管理 |
| serverbase.d | `serverbase/source/served/serverbase.d` | ソース | processNotify |
