# 機能設計書 51-範囲フォーマット

## 概要

本ドキュメントは、clojure-lspにおける範囲フォーマット機能の設計を記述する。この機能はLSPプロトコルの`textDocument/rangeFormatting`リクエストを実装し、選択された範囲のみを対象としたコード整形を提供する。

### 本機能の処理概要

**業務上の目的・背景**：
開発者がコードの一部分のみを整形したいケースは多い。例えば、既存のコードベースに新しいコードを追加した際、追加部分のみを整形したい場合や、特定の関数定義のみをリファクタリング後に整形したい場合がある。ファイル全体をフォーマットすると、意図しない差分が発生したり、コードレビューが困難になることがある。範囲フォーマット機能は、開発者が指定した範囲のみを対象として整形を行うことで、これらの課題を解決する。

**機能の利用シーン**：
1. エディタで特定の行や式を選択し、フォーマットコマンドを実行する場面
2. コピー&ペースト後に貼り付けた部分のみを整形する場面
3. 新規に追加したコードブロックのみを整形し、既存コードへの影響を避ける場面
4. マージコンフリクト解消後、変更部分のみを整形する場面

**主要な処理内容**：
1. クライアントから範囲情報（開始行/列、終了行/列）を含むリクエストを受信
2. 指定範囲に含まれるトップレベルフォームを特定
3. 名前空間のエイリアス情報を取得してcljfmt設定にマージ
4. cljfmtライブラリを使用して対象フォームを整形
5. 整形結果をテキスト編集として返却

**関連システム・外部連携**：
- cljfmtライブラリ：Clojureコードの整形エンジン
- rewrite-cljライブラリ：Clojureコードの解析と操作
- LSPクライアント（エディタ）：範囲選択とフォーマット結果の適用

**権限による制御**：
特に権限による制御はない。設定で`document-range-formatting?`を`false`に設定することで機能を無効化できる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| N/A | エディタ | 主画面 | 範囲選択とフォーマットコマンドの実行 |

## 機能種別

コード変換処理（整形）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| textDocument.uri | string | Yes | 対象ドキュメントのURI | 有効なファイルURI形式 |
| range.start.line | integer | Yes | 整形開始行（0始まり） | 0以上の整数 |
| range.start.character | integer | Yes | 整形開始列（0始まり） | 0以上の整数 |
| range.end.line | integer | Yes | 整形終了行（0始まり） | 0以上の整数 |
| range.end.character | integer | Yes | 整形終了列（0始まり） | 0以上の整数 |

### 入力データソース

- LSPクライアントからの`textDocument/rangeFormatting`リクエスト
- ドキュメントテキスト（dbから取得またはファイルから読み込み）
- cljfmt設定（`.cljfmt.edn`、`:cljfmt`設定、`:style/indent`メタデータ）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| range | Range | 変更範囲（start/end） |
| newText | string | 整形後のテキスト |

### 出力先

LSPクライアントへのレスポンス（TextEdit配列）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ server.cljのjsonrpc.server/receive-requestで"textDocument/rangeFormatting"を受信
2. URI正規化
   └─ normalize-doc-uriでURIを正規化
3. 変更待機
   └─ process-after-changesマクロで未処理の変更があれば待機
4. ハンドラ呼び出し
   └─ handler/range-formattingを呼び出し
5. 範囲情報の構築
   └─ 開始/終了位置をformat-posマップに変換
6. 整形処理
   └─ f.format/range-formattingを呼び出し
7. ZLocの取得
   └─ parser/safe-zloc-of-fileでファイルのZLocを取得
8. 名前空間エイリアス取得
   └─ q/namespace-aliasesで現在の名前空間のエイリアスを取得
9. cljfmt設定マージ
   └─ cljfmt-configにalias-mapをマージ
10. 対象フォーム特定
    └─ 開始位置から終了位置までのトップレベルフォームを抽出
11. 整形実行
    └─ cljfmt/reformat-formで整形
12. レスポンス生成
    └─ 整形結果と範囲をTextEditとして返却
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[URI正規化]
    B --> C{変更待機中?}
    C -->|Yes| D[変更完了まで待機]
    C -->|No| E[handler/range-formatting呼び出し]
    D --> E
    E --> F[ZLoc取得]
    F --> G{ZLoc取得成功?}
    G -->|No| H[nilを返却]
    G -->|Yes| I[名前空間エイリアス取得]
    I --> J[cljfmt設定構築]
    J --> K[開始位置のZLoc取得]
    K --> L[終了位置のZLoc取得]
    L --> M[対象フォーム抽出]
    M --> N[cljfmt/reformat-form実行]
    N --> O[TextEdit生成]
    O --> P[レスポンス返却]
    H --> Q[終了]
    P --> Q
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-51-01 | トップレベルフォーム単位整形 | 選択範囲に含まれるトップレベルフォーム全体を整形対象とする | 常時 |
| BR-51-02 | エイリアス解決 | 名前空間エイリアスをcljfmt設定のalias-mapにマージして整形 | 名前空間が特定できる場合 |
| BR-51-03 | 設定優先順位 | デフォルト設定 < :style/indent < ユーザー設定 の順で上書き | 常時 |
| BR-51-04 | 空結果回避 | パース失敗時やバランスの取れていない括弧の場合はnilを返却 | パース失敗時 |

### 計算ロジック

整形対象フォームの特定ロジック：
1. `start-loc`: 開始位置を含むか左端のZLocを取得
2. `start-top-loc`: `start-loc`のトップレベル親を取得
3. `end-loc`: 終了位置を含むか右端のZLocを取得
4. `end-top-loc`: `end-loc`のトップレベル親を取得
5. `forms`: `start-top-loc`から`end-top-loc`までのフォーム列を抽出

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ドキュメント読み取り | db.documents | SELECT | 対象ファイルのテキスト取得 |
| 解析情報読み取り | db.analysis | SELECT | 名前空間エイリアス取得 |

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

#### db.documents

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | text | uri = 対象URI | ドキュメントのテキスト内容 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | パースエラー | ファイルの構文が不正 | nilを返却（整形なし） |
| - | タイムアウト | 変更待機が60秒超過 | request-failedエラーを返却 |
| - | キャンセル | クライアントがリクエストをキャンセル | request-cancelledエラーを返却 |

### リトライ仕様

リトライは行わない。クライアント側で再度リクエストを発行する。

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

読み取り専用のため、トランザクション管理は不要。

## パフォーマンス要件

- 通常のコードファイルで数百ミリ秒以内に応答
- 変更待機タイムアウトは60秒

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

- ファイルパスの正規化によりパストラバーサルを防止
- `:ignore-paths`設定により特定パスのファイルを処理対象外にできる

## 備考

- cljfmt設定はTTL付きでメモライズされる（3秒）
- `document-range-formatting?`設定でこの機能を無効化可能

---

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

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

### 推奨読解順序

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

まず、範囲フォーマットで使用されるデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | shared.clj | `lib/src/clojure_lsp/shared.clj` | `->range`関数でLSP Range形式への変換を理解 |
| 1-2 | format.clj | `lib/src/clojure_lsp/feature/format.clj` | format-posマップ構造（:row, :col, :end-row, :end-col） |

**読解のコツ**: `format-pos`は1始まりの行/列位置を持つマップで、LSPの0始まりから変換される。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | server.clj | `cli/src/clojure_lsp/server.clj` | **327-332行目**: `textDocument/rangeFormatting`のJSONRPCハンドラ |
| 2-2 | handlers.clj | `lib/src/clojure_lsp/handlers.clj` | **407-419行目**: `range-formatting`関数 |

**主要処理フロー**:
1. **327行目**: `jsonrpc.server/receive-request`で"textDocument/rangeFormatting"リクエストを受信
2. **329行目**: `normalize-doc-uri`でURIを正規化
3. **330行目**: `handler/range-formatting`を呼び出し
4. **331行目**: `conform-or-log`でレスポンスを変換
5. **332行目**: `after-changes`で非同期実行

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | handlers.clj | `lib/src/clojure_lsp/handlers.clj` | **407-419行目**: 範囲情報の構築とformat呼び出し |

**主要処理フロー**:
- **408行目**: `skip-feature-for-uri?`でignore-pathsチェック
- **409-410行目**: `process-after-changes`マクロで変更待機
- **413-414行目**: LSP位置（0始まり）から内部位置（1始まり）への変換
- **415-418行目**: `format-pos`マップの構築
- **419行目**: `f.format/range-formatting`の呼び出し

#### Step 4: フォーマット処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | format.clj | `lib/src/clojure_lsp/feature/format.clj` | **129-156行目**: `range-formatting`関数の実装 |

**主要処理フロー**:
- **130行目**: `parser/safe-zloc-of-file`でファイルのZLocを取得
- **131-135行目**: 名前空間エイリアスを取得してalias-mapを構築
- **136-137行目**: cljfmt設定にalias-mapをマージ
- **138-140行目**: 開始位置のZLocを取得
- **141-144行目**: 終了位置のZLocを取得
- **146-149行目**: 対象フォームを抽出
- **150-151行目**: フォームのスパン（範囲）を計算
- **152-156行目**: `cljfmt/reformat-form`で整形してTextEditを返却

#### Step 5: cljfmt設定解決を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | format.clj | `lib/src/clojure_lsp/feature/format.clj` | **21-33行目**: `resolve-user-cljfmt-config` |
| 5-2 | format.clj | `lib/src/clojure_lsp/feature/format.clj` | **105-117行目**: `resolve-cljfmt-config`と`cljfmt-config` |

**主要処理フロー**:
- **21-33行目**: `.cljfmt.edn`または設定パスからユーザー設定を読み込み
- **83-94行目**: `:style/indent`メタデータから設定を抽出
- **105-112行目**: デフォルト設定、メタデータ設定、ユーザー設定をマージ
- **116-117行目**: TTL付きメモライズで設定をキャッシュ

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

```
textDocument/rangeFormatting (server.clj:327)
    │
    ├─ normalize-doc-uri (server.clj:50-51)
    │
    ├─ handler/range-formatting (handlers.clj:407-419)
    │      │
    │      ├─ skip-feature-for-uri? (handlers.clj:144-151)
    │      │
    │      ├─ process-after-changes (handlers.clj:138-142)
    │      │      └─ wait-for-changes (handlers.clj:63-96)
    │      │
    │      └─ f.format/range-formatting (format.clj:129-156)
    │             │
    │             ├─ parser/safe-zloc-of-file (parser.clj)
    │             │
    │             ├─ q/namespace-aliases (queries.clj)
    │             │
    │             ├─ cljfmt-config (format.clj:116-117)
    │             │      └─ resolve-cljfmt-config (format.clj:105-112)
    │             │             ├─ extract-style-indent-metadata (format.clj:83-94)
    │             │             └─ resolve-user-cljfmt-config (format.clj:21-33)
    │             │
    │             ├─ parser/to-pos (parser.clj)
    │             │
    │             ├─ edit/to-top (refactor/edit.clj)
    │             │
    │             └─ cljfmt/reformat-form (cljfmt)
    │
    └─ conform-or-log (server.clj:63-73)
```

### データフロー図

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

RangeFormatting         ┌─────────────────────────────┐
Request                 │                             │
  ├─ uri ──────────────►│  normalize-doc-uri          │
  ├─ range.start ──────►│                             │
  └─ range.end ────────►│         ↓                   │
                        │                             │
                        │  handler/range-formatting   │
                        │    ├─ format-pos構築        │
                        │    │                        │
                        │    ↓                        │
                        │  f.format/range-formatting  │
db.documents ──────────►│    ├─ ZLoc取得             │
  └─ text               │    ├─ エイリアス取得       │
                        │    ├─ 対象フォーム抽出     │
                        │    │                        │
cljfmt設定 ────────────►│    ├─ cljfmt設定マージ    │
  ├─ .cljfmt.edn        │    │                        │
  ├─ :cljfmt設定        │    └─ cljfmt/reformat-form │
  └─ :style/indent      │                             │         TextEdit[]
                        └─────────────────────────────┘────────►  ├─ range
                                                                  └─ newText
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| server.clj | `cli/src/clojure_lsp/server.clj` | ソース | LSPサーバーのJSONRPCハンドラ定義 |
| handlers.clj | `lib/src/clojure_lsp/handlers.clj` | ソース | 各機能のハンドラ実装 |
| format.clj | `lib/src/clojure_lsp/feature/format.clj` | ソース | フォーマット機能の実装 |
| parser.clj | `lib/src/clojure_lsp/parser.clj` | ソース | Clojureコードのパース処理 |
| queries.clj | `lib/src/clojure_lsp/queries.clj` | ソース | 解析データのクエリ |
| edit.clj | `lib/src/clojure_lsp/refactor/edit.clj` | ソース | ZLoc操作ユーティリティ |
| shared.clj | `lib/src/clojure_lsp/shared.clj` | ソース | 共通ユーティリティ |
| format_test.clj | `lib/test/clojure_lsp/feature/format_test.clj` | テスト | フォーマット機能のテスト |
