# 画面設計書 23-ドキュメントフォーマット（textDocument/formatting）

## 概要

本ドキュメントは、serve-d LSPサーバーにおける `textDocument/formatting` リクエストの処理仕様を定義する画面設計書である。dfmt（D言語フォーマッター）およびsdlfmt（SDLフォーマッター）を使用して、ドキュメント全体のコードフォーマットを実行する機能を記述する。

### 本画面の処理概要

**業務上の目的・背景**：D言語およびSDL（Simple Declarative Language）ファイルのコードスタイルを統一し、可読性を向上させるための自動フォーマット機能である。コーディング規約の遵守を自動化し、チーム開発における一貫性のあるコードスタイルを維持する。dfmtはD言語の標準的なフォーマッターであり、editorconfigやユーザー設定に基づいたカスタマイズが可能である。

**画面へのアクセス方法**：エディタ上で「ドキュメントのフォーマット」コマンド（通常はShift+Alt+F等）を実行する。エディタが `textDocument/formatting` リクエストをLSPサーバーに送信する。

**主要な操作・処理内容**：
1. D言語ファイルの場合: dfmtを使用してドキュメント全体をフォーマットし、差分をTextEdit配列として返却する
2. SDLファイルの場合: sdlfmtを使用してドキュメント全体をフォーマットし、差分をTextEdit配列として返却する
3. フォーマット設定は、ユーザー設定（dfmt固有オプション、editorconfig設定等）に基づいて生成される
4. 差分計算はcharacter-level diffアルゴリズムにより効率的に行われる

**画面遷移**：本機能は単独の操作であり、他の画面への遷移は発生しない。フォーマット結果はTextEdit配列としてエディタに返却され、エディタ側でドキュメントに適用される。

**権限による表示制御**：権限による表示制御は存在しない。ただし、D言語の場合は `config.d.enableFormatting` 設定、SDLの場合は `config.sdl.enableFormatting` 設定によりフォーマット機能の有効/無効を切り替えられる。

## 関連機能

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

## 画面種別

編集操作（LSPプロトコルレスポンス）

## URL/ルーティング

- LSPメソッド: `textDocument/formatting`

## 入出力項目

| 項目名 | 型 | 入力/出力 | 必須 | 説明 |
|--------|-----|----------|------|------|
| params.textDocument.uri | DocumentUri | 入力 | Yes | 対象ドキュメントのURI |
| params.options.tabSize | int | 入力 | Yes | タブサイズ |
| params.options.insertSpaces | bool | 入力 | Yes | スペースインデント使用有無 |
| params.options.trimTrailingWhitespace | Optional!bool | 入力 | No | 末尾空白の削除 |
| params.options.insertFinalNewline | Optional!bool | 入力 | No | 最終行の改行追加 |
| params.options.trimFinalNewlines | Optional!bool | 入力 | No | 最終行の余分な改行削除 |
| TextEdit[] | TextEdit[] | 出力 | - | フォーマットによる差分テキスト編集の配列 |

## 表示項目

| 項目名 | 表示形式 | 説明 |
|--------|---------|------|
| フォーマット結果 | テキスト変更 | ドキュメント全体がフォーマットされた結果としてテキスト編集が適用される |

## イベント仕様

### 1-ドキュメントフォーマット実行

ユーザーが「ドキュメントのフォーマット」を実行した際の処理フロー。

1. ワークスペース設定 (`workspace(uri).config`) を取得する
2. ドキュメントオブジェクトを取得する
3. 言語IDに応じて分岐:

**D言語ファイルの場合（行136-143）**:
   a. `config.d.enableFormatting` がfalseなら空配列を返却
   b. グローバル変数 `gFormattingOptionsApplyOn` に対象URIを、`gFormattingOptions` にフォーマットオプションを設定
   c. `generateDfmtArgs` でdfmt引数を生成（editorconfig上書き設定が有効な場合のみ）
   d. `backend.get!DfmtComponent.format` でdfmtによるフォーマットを実行
   e. ドキュメントの改行コード（eolAt(0)）をdfmt引数に渡す

**SDLファイルの場合（行145-164）**:
   a. `config.sdl.enableFormatting` がfalseなら空配列を返却
   b. `SdlFmtConfig` を構築（インデント、改行コード、等号周りの空白、バックスラッシュインデント等の設定）
   c. `sdlfmt.format` でSDLフォーマットを実行

**共通**:
   d. `diff` 関数でフォーマット前後のテキストの差分を計算し、`TextEdit[]` として返却

### 2-dfmt引数生成

`generateDfmtArgs` 関数（行56-99）でdfmtに渡すコマンドライン引数を生成する。

1. `config.d.overrideDfmtEditorconfig` がfalseなら引数なし（dfmtデフォルト使用）
2. trueの場合、以下の設定からdfmt引数を構築:
   - `config.editor.parseRulers` からmax_line_length / soft_max_line_lengthを決定
   - `gFormattingOptions` からtabSize, insertSpacesを取得
   - dfmt固有設定: alignSwitchStatements, braceStyle, outdentAttributes, spaceAfterCast 等

### 3-差分計算

`diff` 関数（行196-285）でフォーマット前後のテキスト差分を計算する。

1. ドキュメントのrawTextとフォーマット後テキストをcharacter単位で比較
2. 空白文字の追加/削除を優先的に検出する
3. UTF-32文字単位でデコードして比較を行い、マルチバイト文字に対応する
4. 差分をTextEdit配列として返却（各TextEditは変更範囲と新テキスト）

## データベース更新仕様

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

本機能はデータベースに対する操作を行わない。フォーマット結果はTextEdit配列としてクライアントに返却され、クライアント側でドキュメントに適用される。

## メッセージ仕様

| メッセージ | 条件 | 種別 |
|-----------|------|------|
| `attemted to format code that has unsupported language: '{lang}'` | サポートされていない言語のフォーマット要求 | ログ（trace） |

## 例外処理

| 条件 | 挙動 |
|------|------|
| D言語でフォーマット無効設定 | 空の `TextEdit[]` を返却 |
| SDLでフォーマット無効設定 | 空の `TextEdit[]` を返却 |
| サポートされていない言語 | traceログ出力後、空の `TextEdit[]` を返却 |
| dfmtがフォーマットに失敗 | DfmtComponent内で例外が発生する可能性あり |

## 備考

- `gFormattingOptions` および `gFormattingOptionsApplyOn` はグローバル共有変数（`shared`）であり、補完アイテム解決（completionItem/resolve）でも参照される
- `formatCode` 関数（行173-176）と `formatSnippet` 関数（行178-181）はフォーマット関連のユーティリティとして他のモジュールから利用される
- `tryFindFormattingSettings` 関数（行101-128）は、エディタからフォーマット設定が提供されない場合にドキュメント内容からインデント設定を推定する
- diff関数はdfmtの出力が主に空白文字の変更であることを前提に最適化されている

---

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

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

### 推奨読解順序

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

フォーマット処理で使用されるデータ構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | format.d | `source/served/commands/format.d` | `ResolvedFormattingOptions` 構造体（行15-31）: フォーマットオプションの解決済み表現 |
| 1-2 | protocol.d | `source/served/lsp/protocol.d` | `FormattingOptions`, `DocumentFormattingParams`, `TextEdit` の定義を確認 |

**読解のコツ**: `ResolvedFormattingOptions` はLSPの `FormattingOptions` からOptional型を解決した構造体。`gFormattingOptions` として共有される。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | format.d | `source/served/commands/format.d` | `provideFormatting` 関数（行130-171）がLSPリクエストハンドラ |

**主要処理フロー**:
1. **行130**: `@protocolMethod("textDocument/formatting")` でバインド
2. **行133-134**: ワークスペース設定とドキュメントを取得
3. **行136-143**: D言語ファイルの場合のdfmtフォーマット
4. **行145-164**: SDLファイルの場合のsdlfmtフォーマット
5. **行170**: `diff` 関数でフォーマット前後の差分を計算

#### Step 3: dfmt引数生成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | format.d | `source/served/commands/format.d` | `generateDfmtArgs` 関数（行56-99）: editorconfig上書き時のdfmt引数生成 |
| 3-2 | format.d | `source/served/commands/format.d` | `indentString` 関数（行37-54）: インデント文字列の生成 |

**主要処理フロー**:
- **行59**: `overrideDfmtEditorconfig` チェック
- **行61-72**: rulers設定からmax/softMaxLineLengthを決定
- **行75-95**: dfmtオプションを配列として構築

#### Step 4: 差分計算を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | format.d | `source/served/commands/format.d` | `diff` 関数（行196-285）: フォーマット前後のcharacter-level差分計算 |

**主要処理フロー**:
- **行228**: beforeとafterをwhileループで走査
- **行245-254**: 同じ文字の場合はスキップ、差分があれば蓄積
- **行263-264**: 空白文字の追加/削除を優先検出
- **行283**: 最後に残った差分をpush

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

```
textDocument/formatting リクエスト
    |
    +-- provideFormatting (format.d:130)
           |
           +-- workspace(uri).config (設定取得)
           +-- documents[uri] (ドキュメント取得)
           |
           +-- [D言語の場合]
           |      +-- generateDfmtArgs (format.d:56)
           |      +-- backend.get!DfmtComponent.format (dfmt実行)
           |
           +-- [SDLの場合]
           |      +-- sdlfmt.format (SDLフォーマット実行)
           |
           +-- diff (format.d:196)
                  +-- document.movePositionBytes (位置変換)
                  +-- TextEdit[] 構築
```

### データフロー図

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

DocumentFormattingParams -----> provideFormatting ----------------> TextEdit[]
  textDocument.uri                |
  options (tabSize等)             +-- 設定取得
                                  +-- 言語判定
                                  +-- dfmt/sdlfmt実行
                                  +-- diff (差分計算)
                                  +-- TextEdit配列生成
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| format.d | `source/served/commands/format.d` | ソース | フォーマット主処理・差分計算・dfmt引数生成 |
| types.d | `source/served/types.d` | ソース | 共通型定義・UserConfiguration等 |
| protocol.d | `source/served/lsp/protocol.d` | ソース | LSPプロトコル型定義 |
| extension.d | `source/served/extension.d` | ソース | workspace関数・backend等 |
| textdocumentmanager.d | `source/served/lsp/textdocumentmanager.d` | ソース | ドキュメント管理・位置変換 |
