# 通知設計書 3-textDocument/didSave

## 概要

本ドキュメントは、LSP(Language Server Protocol)におけるtextDocument/didSave通知の設計仕様を記載する。クライアントからサーバーへ送信される通知であり、テキストドキュメントが保存されたことをサーバーに通知する。

### 本通知の処理概要

textDocument/didSave通知は、エディタ上でClojure/ClojureScriptファイルが保存された際に、クライアントからclojure-lspサーバーへ送信される通知である。clojure-lspの現在の実装では、この通知受信時に限定的な処理のみを行う。

**業務上の目的・背景**：ファイルの保存はユーザーの意図的なアクションであり、この時点でのドキュメント状態は「確定された状態」とみなせる。保存をトリガーとした追加処理（設定ファイル変更時の全ファイル再解析など）を実行するための契機となる。また、ディスク上の状態とメモリ上の状態の同期を管理する目的もある。

**通知の送信タイミング**：ユーザーがエディタでファイルを保存した直後（Ctrl+S、Cmd+S、または自動保存）に、エディタ(LSPクライアント)がこの通知を送信する。

**通知の受信者**：clojure-lspサーバープロセスがこの通知を受信する。サーバーはfuture内で非同期処理を実行する。

**通知内容の概要**：通知には、保存されたドキュメントのURIが含まれる。オプションでドキュメントのテキスト内容も含めることが可能（サーバー設定による）。

**期待されるアクション**：サーバーは受信後、ドキュメントの保存状態フラグを更新する。また、.clj-kondoやconfig.ednなどの設定ファイルが保存された場合は、開いている全ファイルの再解析を実行し、診断情報を更新する。

## 通知種別

LSP通知 (クライアント → サーバー)

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（future内で処理） |
| 優先度 | 中（保存時の追加処理） |
| リトライ | なし（通知のため応答なし） |

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

クライアントからサーバーへの一方向通知のため、送信先決定ロジックは不要。

## 通知テンプレート

### パラメータ構造

| 項目 | 内容 |
|-----|------|
| メソッド名 | textDocument/didSave |
| パラメータ型 | DidSaveTextDocumentParams |
| プロトコル | JSON-RPC 2.0 over stdio |

### パラメータ定義

```json
{
  "textDocument": {
    "uri": "file:///path/to/file.clj"
  }
}
```

### 添付ファイル

該当なし（LSP通知にファイル添付機能なし）

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| textDocument.uri | ドキュメントのURI | クライアント（エディタ） | Yes |
| text | ドキュメントのテキスト（オプション） | クライアント | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | ファイル保存 | ドキュメントがオープン中 | ユーザーがCtrl+S/Cmd+S押下時 |
| 画面操作 | 自動保存 | 自動保存設定が有効 | エディタの自動保存機能 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| ドキュメント未オープン | didOpenが送信されていない場合 |
| サーバー未接続 | LSPサーバーが起動していない/接続されていない場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ファイル保存] --> B[textDocument/didSave通知送信]
    B --> C[サーバーで受信]
    C --> D[URIを正規化]
    D --> E[handler/did-saveを呼び出し]
    E --> F[saved-on-diskフラグをtrueに設定]
    F --> G{設定ファイルか?}
    G -->|Yes| H[全オープンファイルを再解析]
    G -->|No| I[終了]
    H --> J[診断情報を公開]
    J --> K[CodeLensをリフレッシュ]
    K --> I
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| db/documents | オープン中ドキュメントの参照 | 設定ファイル保存時に使用 |
| db/project-root-uri | プロジェクトルートの参照 | 設定ファイルパス解決に使用 |

### テーブル別参照項目詳細

#### db/documents

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| 全ドキュメントのURI | 再解析対象の特定 | :v が存在するドキュメント |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| db/documents | UPDATE | 保存状態フラグの更新 |
| db/analysis | UPDATE | 設定ファイル変更時の再解析結果 |
| db/diagnostics | UPDATE | 診断情報の更新 |

#### db/documents更新詳細

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| ASSOC | [:documents uri :saved-on-disk] | true | ディスクに保存済み |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 処理中の例外 | 再解析処理の失敗 | ログ出力、例外を再throw |
| URI解決エラー | 無効なファイルパス | ログ出力して処理継続 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（通知のためリトライなし） |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | なし |
| 1日あたり上限 | なし |

### 配信時間帯

制限なし（常時処理可能）

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

- ファイルパス(URI)はユーザーのローカルファイルシステム上のパスを含む
- 設定ファイルの変更は全オープンファイルの再解析をトリガーする可能性がある

## 備考

- LSP仕様: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didSave
- この通知は応答を返さない(notification)
- 通知一覧.csvの備考欄には「現在は何も処理しない」と記載されているが、実際には設定ファイル変更時の再解析処理が実装されている

---

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

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

### 推奨読解順序

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

まず、プログラム間で受け渡されるデータ構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | db.clj | `lib/src/clojure_lsp/db.clj` | :documents構造の:saved-on-diskフラグ |

**読解のコツ**: 保存状態フラグの意味と、それがどのように使用されるかを理解すること。

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

処理の起点となるファイル・関数を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | server.clj | `cli/src/clojure_lsp/server.clj` | receive-notification multimethod定義 |

**主要処理フロー**:
1. **260-266行目**: `textDocument/didSave`メソッドのdefmethod定義
2. **261-266行目**: future内でhandler/did-saveを呼び出し、例外処理

#### Step 3: ハンドラー層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | handlers.clj | `lib/src/clojure_lsp/handlers.clj` | did-save関数の定義 |

**主要処理フロー**:
- **233-236行目**: did-save関数本体
- **236行目**: f.file-management/did-saveへの委譲

#### Step 4: ファイル管理機能層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | file_management.clj | `lib/src/clojure_lsp/feature/file_management.clj` | did-save関数の実装 |
| 4-2 | file_management.clj | `lib/src/clojure_lsp/feature/file_management.clj` | lint-opened-files-when-any-config-changed!関数 |

**主要処理フロー**:
- **398-400行目**: did-save関数本体
- **399行目**: saved-on-diskフラグをtrueに設定
- **400行目**: 設定ファイル変更時の再解析処理呼び出し
- **378-396行目**: lint-opened-files-when-any-config-changed!関数

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

```
jsonrpc.server/receive-notification "textDocument/didSave"
    |
    +-- server.clj (260-266行目)
           |
           +-- future (非同期実行)
                  |
                  +-- handler/did-save (handlers.clj 233-236行目)
                         |
                         +-- f.file-management/did-save (file_management.clj 398-400行目)
                                |
                                +-- swap! db* [:documents uri :saved-on-disk] (399行目)
                                +-- lint-opened-files-when-any-config-changed! (400行目)
                                       |
                                       +-- 設定ファイル判定 (381-388行目)
                                       +-- analyze-reference-uris! (394行目)
                                       +-- f.diagnostic/publish-all-diagnostics! (395行目)
                                       +-- producer/refresh-code-lens (396行目)
```

### データフロー図

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

DidSaveTextDocumentParams
    |
    +-- uri --------+
                    |
                    +---> normalize-doc-uri
                    |            |
                    |            v
                    +---> swap! db* [:documents uri :saved-on-disk] = true
                    |            |
                    |            v
                    +---> lint-opened-files-when-any-config-changed!
                                 |
                                 +---> 設定ファイルか判定
                                 |            |
                                 |            v (設定ファイルの場合)
                                 +---> analyze-reference-uris! (全オープンファイル)
                                 |            |
                                 |            v
                                 +---> publish-all-diagnostics! -> textDocument/publishDiagnostics
                                 |            |
                                 |            v
                                 +---> refresh-code-lens -> workspace/codeLens/refresh
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| server.clj | `cli/src/clojure_lsp/server.clj` | ソース | LSPサーバーのエントリーポイント、通知受信 |
| handlers.clj | `lib/src/clojure_lsp/handlers.clj` | ソース | 各種LSPメッセージのハンドラー |
| file_management.clj | `lib/src/clojure_lsp/feature/file_management.clj` | ソース | ファイル管理機能の実装 |
| diagnostics.clj | `lib/src/clojure_lsp/feature/diagnostics.clj` | ソース | 診断情報の公開 |
| producer.clj | `lib/src/clojure_lsp/producer.clj` | ソース | CodeLensリフレッシュ通知 |
