# 帳票設計書 5-バッファエクスポート（テキストファイル）

## 概要

本ドキュメントは、Windows Terminal のタブのターミナルバッファ全体の内容をUTF-8テキストファイル（.txt）としてエクスポートする処理の設計を記述する。ファイル保存ダイアログでダウンロードフォルダに保存される。

### 本帳票の処理概要

アクティブなタブのターミナルバッファ全体の内容を読み取り、UTF-8エンコードのテキストファイル（.txt）として指定されたパスに保存する。ファイルパスが指定されていない場合はファイル保存ダイアログを表示し、ユーザーにファイル名と保存先を選択させる。

**業務上の目的・背景**：ターミナルセッションの完全な出力ログを永続化するための機能を提供する。コマンドの実行結果やログ出力の全体を後から参照・共有するために、テキストファイルとして保存できるようにする。選択範囲ではなくバッファ全体を対象とする点がクリップボードコピーとの主な違い。

**帳票の利用シーン**：ユーザーがターミナルのセッション内容全体をファイルに保存したい場合に使用される。メニューまたはキーバインドから「Export Text」アクション経由で呼び出される。長時間のビルドログ、デバッグ出力、セッション記録の保存に利用される。

**主要な出力内容**：
1. バッファの先頭行から最終非空白行までの全テキスト
2. 各行の末尾空白はトリミングされる
3. 非折り返し行の末尾に改行コード（\r\n）が付加される
4. 折り返し行は改行なしで連結される

**帳票の出力タイミング**：ユーザーが「Export Text」アクション（exportBuffer アクション）を実行した際に出力される。

**帳票の利用者**：ターミナルセッションのログを保存・共有したいすべてのユーザー。

## 帳票種別

ファイルエクスポート（テキストファイル出力）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | ターミナルペイン | - | メニュー「Export Text」/ キーバインド / コマンドパレット |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | プレーンテキスト（.txt） |
| 用紙サイズ | 該当なし |
| 向き | 該当なし |
| ファイル名 | {タブタイトル}.txt（デフォルト）、ユーザー変更可能 |
| 出力方法 | ファイル保存ダイアログ経由のファイル書き込み、またはパス直接指定 |
| 文字コード | UTF-8 |

### PDF固有設定

該当なし

### Excel固有設定

該当なし

## 帳票レイアウト

### レイアウト概要

バッファ全体のテキストを行ごとに出力する。選択範囲ではなくバッファ全体が対象。

```
┌─────────────────────────────────────┐
│  行1テキスト（末尾空白トリミング済）\r\n  │
│  行2テキスト（折り返し行 → 改行なし）     │
│  行3テキスト（非折り返し行）\r\n          │
│  ...                                │
│  最終非空白行テキスト\r\n               │
└─────────────────────────────────────┘
```

### ヘッダー部

該当なし

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|------|
| 1 | 行テキスト | 各行のバッファテキスト（末尾空白トリミング済み） | ROW::GetText() | プレーンテキスト | 可変 |
| 2 | 改行コード | 非折り返し行の末尾改行 | ROW::WasWrapForced() で判定 | \r\n | 2バイト |

### フッター部

該当なし

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| タブ（tab） | エクスポート対象のタブ | Yes |
| ファイルパス（filepath） | 保存先パス（空の場合はダイアログ表示） | No |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | 行番号（バッファ先頭から順序通り） | 昇順 |

### 改ページ条件

該当なし

## データベース参照仕様

### 参照テーブル一覧

該当なし（メモリ上の TextBuffer から直接データを取得する）

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

#### TextBuffer（メモリ上バッファ）

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| ROW::GetText() | 行テキスト | 各行（0 ~ lastRow） | 末尾空白の最後の位置まで取得 |
| ROW::WasWrapForced() | 折り返し判定 | 各行 | false: 改行追加、true: 改行なし |
| textBuffer.GetLastNonSpaceCharacter().y | 最終行番号 | 1回のみ | 空白のみの末尾行をスキップ |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| strEnd | rowText.find_last_not_of(UNICODE_SPACE) | 該当なし | 末尾空白の除去位置を特定 |
| トリミング済テキスト | rowText.substr(0, strEnd + 1) | 該当なし | strEnd == npos の場合は空行 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[Export Text アクション実行] --> B[AppActionHandlers::_ExportTab]
    B --> C{filepath 指定あり?}
    C -->|No| D[ファイル保存ダイアログ表示]
    C -->|Yes| E[環境変数を展開]
    D --> F{パス選択された?}
    F -->|No| G[処理中断]
    F -->|Yes| H[パス確定]
    E --> H
    H --> I[control.ReadEntireBuffer 呼び出し]
    I --> J[ControlCore::ReadEntireBuffer]
    J --> K[ターミナルロック取得]
    K --> L[行ループ 0 ~ lastRow]
    L --> M[ROW::GetText で行テキスト取得]
    M --> N[末尾空白トリミング]
    N --> O{折り返し行?}
    O -->|No| P[\\r\\n 追加]
    O -->|Yes| Q[改行なし]
    P --> R[次の行へ]
    Q --> R
    R --> S{最終行?}
    S -->|No| L
    S -->|Yes| T[hstring として返却]
    T --> U[til::u16u8 で UTF-8 変換]
    U --> V[write_utf8_string_to_file_atomic でファイル書き込み]
    V --> W[終了]
    G --> W
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| コントロール未取得 | tab.GetActiveTerminalControl() が null | なし | 処理をスキップ |
| ダイアログキャンセル | ユーザーがファイル保存ダイアログをキャンセル | なし | 処理を中断 |
| ファイル書き込みエラー | write_utf8_string_to_file_atomic の失敗 | なし | CATCH_LOG() でログ記録 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 最大9001行（デフォルトスクロールバックバッファ） |
| 目標出力時間 | バッファサイズに依存。非同期コルーチン（safe_void_coroutine）で実行されるためUIはブロックされない |
| 同時出力数上限 | 1（ターミナルロック取得による排他制御） |

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

- ファイル保存ダイアログを使用する場合、ユーザーが保存先を選択するため安全
- パスが直接指定される場合（設定から）、環境変数の展開が行われる（wil::ExpandEnvironmentStringsW）
- ファイル書き込みはアトミック操作（write_utf8_string_to_file_atomic）で行われ、部分書き込みのリスクを低減
- GH#11356: UWP API ではなく shell32 のファイルピッカーを使用。UWP API は昇格（elevated）環境で動作しないため
- ファイルフィルターは .txt と *.* の2種類をサポート

## 備考

- _ExportTab は safe_void_coroutine（コルーチン）として実装されており、UIスレッドをブロックしない
- デフォルトの保存先はダウンロードフォルダ（FOLDERID_Downloads）
- ファイル名のデフォルトはタブのタイトル + ".txt"
- タブタイトルは til::clean_filename() でファイル名に使えない文字がクリーニングされる
- ReadEntireBuffer は選択範囲ではなくバッファ全体（行0から最終非空白行まで）を対象とする
- ControlCore::ReadEntireBuffer と TermControl::ReadEntireBuffer の2段階の呼び出しがある

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | textBuffer.hpp | `src/buffer/out/textBuffer.hpp` | TextBuffer クラスの GetLastNonSpaceCharacter(), GetRowByOffset() メソッドを理解する |

**読解のコツ**: ReadEntireBuffer は CopyRequest を使用せず、直接行ループでバッファを走査する。GetPlainText とは異なるアプローチ。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | TabManagement.cpp | `src/cascadia/TerminalApp/TabManagement.cpp` | _ExportTab メソッド（311-371行目）。ファイル保存ダイアログの構成、ReadEntireBuffer 呼び出し、ファイル書き込み |

**主要処理フロー**:
1. **315-318行目**: ファイルフィルター定義（.txt, *.*）
2. **326行目**: アクティブターミナルコントロール取得
3. **330-361行目**: ファイルパスの決定（ダイアログ or 直接指定）
4. **365行目**: control.ReadEntireBuffer() 呼び出し
5. **366行目**: UTF-8変換 + ファイル書き込み（write_utf8_string_to_file_atomic）

#### Step 3: ReadEntireBuffer を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ControlCore.cpp | `src/cascadia/TerminalControl/ControlCore.cpp` | ReadEntireBuffer メソッド（2339-2364行目）。ターミナルロック取得、行ループ、末尾空白トリミング、折り返し行判定 |

**主要処理フロー**:
- **2341行目**: ターミナルロック取得
- **2343行目**: textBuffer 参照取得
- **2346行目**: 最終非空白行の y座標を取得
- **2347行目**: 行ループ（0 から lastRow まで）
- **2349-2350行目**: 行テキスト取得と末尾空白位置の検索
- **2351-2355行目**: 末尾空白トリミングしてバッファに追加
- **2357-2360行目**: 折り返し行でない場合に \r\n を追加

#### Step 4: AppActionHandlers を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | AppActionHandlers.cpp | `src/cascadia/TerminalApp/AppActionHandlers.cpp` | 1209-1216行目。exportBuffer アクションのハンドラ。パス指定の有無により _ExportTab を呼び出す |

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

```
AppActionHandlers (exportBuffer アクション)
    |
    +-- TerminalPage::_ExportTab (TabManagement.cpp:311)
            |
            +-- SaveFilePicker (ファイル保存ダイアログ)
            |       +-- SHGetKnownFolderItem(FOLDERID_Downloads)
            |       +-- til::clean_filename()
            |
            +-- TermControl::ReadEntireBuffer (TermControl.cpp:3702)
            |       |
            |       +-- ControlCore::ReadEntireBuffer (ControlCore.cpp:2339)
            |               |
            |               +-- Terminal::LockForWriting()
            |               +-- TextBuffer::GetLastNonSpaceCharacter()
            |               +-- TextBuffer::GetRowByOffset(rowIndex)
            |               +-- ROW::GetText()
            |               +-- ROW::WasWrapForced()
            |
            +-- til::u16u8() (UTF-16 -> UTF-8 変換)
            +-- til::io::write_utf8_string_to_file_atomic()
```

### データフロー図

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

TextBuffer (メモリ)        ---> ReadEntireBuffer()           ---> hstring (全バッファテキスト)
  |                                |                                |
  +-- ROW[]                      +-- 行ループ(0~lastRow)          +-- til::u16u8()
  +-- lastRow                    +-- 末尾空白トリミング                |
                                 +-- 折り返し判定                    +-- UTF-8テキスト
exportBuffer アクション                                              |
  +-- filepath (オプション)   ---> _ExportTab()               ---> write_utf8_string_to_file_atomic()
  +-- tab (対象タブ)              +-- ファイルダイアログ                |
                                                                   +-- {タブタイトル}.txt
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| TabManagement.cpp | `src/cascadia/TerminalApp/TabManagement.cpp` | ソース | _ExportTab の実装（ファイルダイアログ、書き込み） |
| AppActionHandlers.cpp | `src/cascadia/TerminalApp/AppActionHandlers.cpp` | ソース | exportBuffer アクションのハンドラ |
| ControlCore.cpp | `src/cascadia/TerminalControl/ControlCore.cpp` | ソース | ReadEntireBuffer の実装 |
| TermControl.cpp | `src/cascadia/TerminalControl/TermControl.cpp` | ソース | ReadEntireBuffer のUI層ラッパー |
| TermControl.h | `src/cascadia/TerminalControl/TermControl.h` | ヘッダー | ReadEntireBuffer のシグネチャ |
| ControlCore.h | `src/cascadia/TerminalControl/ControlCore.h` | ヘッダー | ReadEntireBuffer のシグネチャ |
| TerminalPage.h | `src/cascadia/TerminalApp/TerminalPage.h` | ヘッダー | _ExportTab のシグネチャ |
| textBuffer.hpp | `src/buffer/out/textBuffer.hpp` | ヘッダー | TextBuffer クラス定義 |
