# 機能設計書 29-ドキュメントフォーマット (textDocument/formatting)

## 概要

本ドキュメントは、serve-d LSPサーバーにおけるドキュメントフォーマット機能（textDocument/formatting）の設計を記述する。D言語ソース（dfmt使用）およびSDLファイル（sdlfmt使用）のコードフォーマットを実行する機能である。

### 本機能の処理概要

**業務上の目的・背景**：コードフォーマットは、コードの可読性を統一し、チーム開発における一貫したコーディングスタイルを維持するために重要である。手動でのフォーマット調整は非効率かつミスが生じやすいため、自動フォーマット機能がLSPサーバーに求められる。

**機能の利用シーン**：開発者がエディタ上で「Format Document」（Shift+Alt+F等）コマンドを実行した際に呼び出される。また、保存時の自動フォーマット設定が有効な場合にも呼び出される。

**主要な処理内容**：
1. ドキュメントの言語IDに基づき、適切なフォーマッタを選択する（D言語: dfmt、SDL: sdlfmt）
2. フォーマットオプション（タブサイズ、インデント種別等）をフォーマッタのパラメータに変換する
3. フォーマッタを実行し、フォーマット結果を取得する
4. 元のドキュメントとフォーマット結果のdiff（差分）を計算し、TextEdit配列として返却する

**関連システム・外部連携**：DfmtComponent（D言語フォーマッタ）、sdlfmt（SDLフォーマッタ）

**権限による制御**：設定 `config.d.enableFormatting` でD言語フォーマットを制御、`config.sdl.enableFormatting` でSDLフォーマットを制御。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 23 | ドキュメントフォーマット（textDocument/formatting） | 主画面 | dfmt（D言語）およびsdlfmt（SDLファイル）によるコードフォーマットを実行する |

## 機能種別

変換処理（コードフォーマット）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| textDocument.uri | DocumentUri | Yes | 対象ドキュメントのURI | 有効なドキュメントURIであること |
| options.tabSize | int | Yes | タブのスペース数 | - |
| options.insertSpaces | bool | Yes | タブの代わりにスペースを使用するか | - |
| options.trimTrailingWhitespace | bool | No | 末尾空白を除去するか | - |
| options.insertFinalNewline | bool | No | 最終行に改行を挿入するか | - |
| options.trimFinalNewlines | bool | No | 末尾の空行を除去するか | - |

### 入力データソース

- LSPクライアントからのtextDocument/formattingリクエストのDocumentFormattingParams
- ドキュメントマネージャに保持されたドキュメントのテキストデータ
- ワークスペース設定（dfmtオプション、エディタ設定）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| range | TextRange | 編集対象の位置範囲 |
| newText | string | 新しいテキスト |

### 出力先

LSPクライアント（JSON-RPCレスポンスとして返却）

## 処理フロー

### 処理シーケンス

```
1. textDocument/formattingリクエスト受信
   └─ DocumentFormattingParamsからURI・オプションを取得
2. ワークスペース設定とドキュメント取得
   └─ workspace(uri).config, documents[uri]
3. 言語判定
   └─ getLanguageId() で "d" / "sdl" / その他 を判定
4A. D言語フォーマット（languageId == "d"）
   4A-1. enableFormattingチェック
   4A-2. gFormattingOptionsの更新（タブサイズ・インデント設定）
   4A-3. generateDfmtArgsでdfmtオプション配列生成
   4A-4. DfmtComponent.format呼び出し
4B. SDLフォーマット（languageId == "sdl"）
   4B-1. enableFormattingチェック（sdl.enableFormatting）
   4B-2. SdlFmtConfig構築
   4B-3. sdlfmt.format呼び出し
4C. その他の言語
   └─ 空配列を返却
5. diff計算
   └─ 元のドキュメントとフォーマット結果の差分をTextEdit配列に変換
6. 結果返却
```

### フローチャート

```mermaid
flowchart TD
    A[formatting リクエスト受信] --> B[設定・ドキュメント取得]
    B --> C{languageId?}
    C -->|d| D{enableFormatting?}
    C -->|sdl| E{sdl.enableFormatting?}
    C -->|other| F[空配列返却]
    D -->|No| F
    D -->|Yes| G[gFormattingOptions更新]
    G --> H[generateDfmtArgs]
    H --> I[DfmtComponent.format]
    E -->|No| F
    E -->|Yes| J[SdlFmtConfig構築]
    J --> K[sdlfmt.format]
    I --> L[diff計算]
    K --> L
    L --> M[TextEdit配列返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-29-01 | D言語フォーマット制御 | enableFormattingが無効の場合は空配列を返す | D言語ファイル（138行目） |
| BR-29-02 | SDLフォーマット制御 | sdl.enableFormattingが無効の場合は空配列を返す | SDLファイル（147行目） |
| BR-29-03 | グローバル設定更新 | フォーマット実行時にgFormattingOptionsApplyOnとgFormattingOptionsを更新する | D言語フォーマット時（140-141行目） |
| BR-29-04 | overrideDfmtEditorconfig | この設定が有効な場合、エディタ設定をdfmtオプションに上書きする | dfmtオプション生成時（59行目） |
| BR-29-05 | 改行コード保持 | ドキュメントの既存改行コード（eolAt(0)）を使用する | フォーマッタ呼び出し時 |
| BR-29-06 | diff最適化 | フォーマット結果と元テキストの差分のみをTextEditとして返す | 結果返却時 |

### 計算ロジック

dfmtオプション生成（generateDfmtArgs）では以下のパラメータを設定する:
- align_switch_statements
- brace_style
- end_of_line（ドキュメントの既存改行コード）
- indent_size, indent_style
- max_line_length, soft_max_line_length（editor.rulers設定から計算）
- outdent_attributes, space_after_cast, split_operator_at_line_end
- tab_width, selective_import_space, space_before_function_parameters
- compact_labeled_statements, template_constraint_style
- single_template_constraint_indent, space_before_aa_colon
- keep_line_breaks, single_indent

max_line_lengthの計算:
- rulers設定が1つ: maxLineLength = softMaxLineLength = rulers[0]
- rulers設定が2つ以上: maxLineLength = rulers[最後], softMaxLineLength = rulers[最後-1]
- デフォルト: maxLineLength = 120, softMaxLineLength = 80

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

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

データベース操作は行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 空結果 | フォーマット機能が無効の場合 | 空配列を返却 |
| - | 空結果 | 未対応言語の場合 | traceログ出力、空配列を返却（167行目） |
| - | 空結果 | フォーマット結果が元と同一の場合 | 空のTextEdit配列を返却 |

### リトライ仕様

リトライは不要。

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

トランザクションは使用しない。

## パフォーマンス要件

- DfmtComponent.formatはgetYieldで非同期待機する
- sdlfmt.formatは同期処理
- diff関数は文字単位の比較を行うため、大規模ファイルではレイテンシが増大する可能性がある
- diff関数はUTF-32デコードを正しく処理する

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

特になし。

## 備考

- gFormattingOptionsとgFormattingOptionsApplyOnはshared変数として定義されており、複数のファイバーから参照される可能性がある
- tryFindFormattingSettings関数（101-128行目）はドキュメントのインデント解析から設定を推測する補助関数で、フォーマットオプションが明示的に提供されない場合に使用される
- diff関数（196-285行目）はユニットテストが充実しており（287-444行目）、基本的なテキスト差分から複雑なフォーマット差分まで検証されている
- formatCode関数（173-176行目）とformatSnippet関数（178-181行目）は他のモジュール（complete.d等）から利用される共通関数

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | format.d | `source/served/commands/format.d` | ResolvedFormattingOptions構造体（15-31行目）: tabSize, insertSpaces等のフォーマット設定の内部表現 |
| 1-2 | format.d | `source/served/commands/format.d` | グローバル変数gFormattingOptionsApplyOn, gFormattingOptions（33-34行目）: 現在適用中のフォーマット設定 |

**読解のコツ**: `shared`修飾子（33-34行目）はD言語のスレッド安全性機構で、複数スレッドからアクセスされる変数に使用する。ファイバー環境では実質的にグローバル状態として機能する。

#### Step 2: フォーマットオプション生成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | format.d | `source/served/commands/format.d` | generateDfmtArgs関数（56-99行目）でユーザー設定からdfmtコマンドライン引数を生成する |
| 2-2 | format.d | `source/served/commands/format.d` | tryFindFormattingSettings関数（101-128行目）でドキュメントのインデントパターンを解析する |

**主要処理フロー**:
- **59行目**: overrideDfmtEditorconfigチェック
- **61-72行目**: rulers設定からmax/softMaxLineLengthを計算
- **75-95行目**: dfmtオプション配列の構築（17個のパラメータ）

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | format.d | `source/served/commands/format.d` | provideFormatting関数（130-171行目）がLSPプロトコルメソッドのエントリーポイント |

**主要処理フロー**:
1. **133-134行目**: ワークスペース設定とドキュメント取得
2. **136行目**: D言語チェック
3. **138-143行目**: D言語フォーマット（enableFormatting確認、gFormattingOptions更新、DfmtComponent.format呼び出し）
4. **145-164行目**: SDLフォーマット（sdl.enableFormatting確認、SdlFmtConfig構築、sdlfmt.format呼び出し）
5. **170行目**: `diff(document, result)` 差分計算

#### Step 4: diff関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | format.d | `source/served/commands/format.d` | diff関数（196-285行目）で元テキストとフォーマット結果の差分をTextEdit配列に変換する |

**主要処理フロー**:
- **201行目**: 元テキスト（before）の取得
- **228行目**: while (i < before.length || j < after.length) で文字単位比較ループ
- **245-254行目**: 一致する場合は前進し、蓄積された編集をプッシュ
- **263-280行目**: 不一致の場合、空白文字の追加/削除を優先判定してTextEditを蓄積
- **283行目**: 最後の蓄積された編集をプッシュ

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

```
provideFormatting (format.d:131)
    |
    +-- [D言語]
    |   +-- generateDfmtArgs (format.d:56)
    |   +-- DfmtComponent.format (workspaced/com/dfmt.d)
    |
    +-- [SDL]
    |   +-- sdlfmt.format (sdlfmtライブラリ)
    |
    +-- diff (format.d:196)
            +-- document.movePositionBytes
            +-- TextEdit構築
```

### データフロー図

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

DocumentFormattingParams --> provideFormatting -----------------> TextEdit[]
  (uri, options)                   |
                                   v
                            言語判定 (d / sdl / other)
                                   |
                        [D言語]    |    [SDL]
                            v             v
                     DfmtComponent   sdlfmt.format
                     .format              |
                            |             |
                            v             v
                         フォーマット済みテキスト
                                   |
                                   v
                            diff (元テキスト, フォーマット済み)
                                   |
                                   v
                            TextEdit[] (差分のみ)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| format.d | `source/served/commands/format.d` | ソース | ドキュメントフォーマットのメイン実装（provideFormatting: 130-171行目, diff: 196-285行目, generateDfmtArgs: 56-99行目） |
| types.d | `source/served/types.d` | ソース | LSP型定義（DocumentFormattingParams, TextEdit等） |
| code_actions.d | `source/served/commands/code_actions.d` | ソース | 本機能のgenereateDfmtArgsを利用（13行目のインポート） |
| complete.d | `source/served/commands/complete.d` | ソース | formatCode/formatSnippet関数を利用 |
| dfmt.d (workspaced) | `workspace-d/source/workspaced/com/dfmt.d` | ソース | DfmtComponent.format APIの実装 |
