# 機能設計書 4-スクロールバック

## 概要

本ドキュメントは、Ghosttyターミナルエミュレータにおける履歴バッファを保持し、過去の出力にスクロールしてアクセスできる「スクロールバック」機能の設計を記述する。

### 本機能の処理概要

スクロールバック機能は、PageList構造体によって実装される効率的なメモリ管理システムで、端末の出力履歴を保持する。画面からスクロールアウトした行はスクロールバックバッファに保存され、ユーザーは過去の出力内容を確認できる。メモリプールを使用した効率的なページ管理により、大量の履歴を低メモリオーバーヘッドで保持する。

**業務上の目的・背景**：ターミナルユーザーにとって、過去のコマンド出力を参照することは日常的な作業である。ログの確認、エラーメッセージの確認、以前の出力との比較など、スクロールバック機能は生産性に直結する重要な機能である。また、メモリ効率の良い実装により、長時間のセッションでも安定した動作を実現する。

**機能の利用シーン**：
- ユーザーがマウスホイールやキーボードで過去の出力にスクロールする場面
- 長いコマンド出力を確認するためにページアップ/ページダウンする場面
- 検索機能で過去の出力内容を検索する場面
- スクロールバックをファイルに書き出す場面

**主要な処理内容**：
1. ページリスト（双方向リンクリスト）によるページ管理
2. メモリプールを使用した効率的なメモリ確保/解放
3. ビューポートのピン位置管理
4. 最大サイズ制限に基づく古いページの自動削除
5. ページのシリアル番号による有効性追跡

**関連システム・外部連携**：
- Screen構造体との連携
- Terminal構造体からの呼び出し
- レンダラーへのビューポート情報提供
- 検索機能との連携

**権限による制御**：特になし。スクロールバックは全ユーザーに対して同一の動作を提供する。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | メインターミナルウィンドウ | 主画面 | 履歴バッファの保持と過去の出力へのスクロールアクセス |

## 機能種別

データ構造管理 / メモリ管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| cols | size.CellCountInt | Yes | 画面の列数 | 1以上の正の整数 |
| rows | size.CellCountInt | Yes | 画面の行数 | 1以上の正の整数 |
| max_size | ?usize | No | 最大サイズ（バイト） | nullで無制限 |

### 入力データソース

- Screen構造体からの初期化パラメータ
- 設定ファイルからのスクロールバック制限

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| pages | List | ページの双方向リンクリスト |
| total_rows | usize | 総行数（アクティブ領域+スクロールバック） |
| page_size | usize | 総ページサイズ（バイト） |
| viewport | Viewport | ビューポート位置 |

### 出力先

- Screen構造体への状態提供
- レンダラーへのビューポートデータ提供

## 処理フロー

### 処理シーケンス

```
1. PageList初期化
   └─ MemoryPool初期化（ノード、ページ、ピン用）
   └─ 初期ページ作成（ビューポート分）
   └─ ビューポートピン作成とトラッキング

2. 新規行追加（スクロール発生時）
   └─ 現在ページに空きがあれば追加
   └─ 空きがなければ新規ページ作成
   └─ 最大サイズ超過時は古いページを削除

3. ビューポート移動
   └─ active: アクティブ領域に固定
   └─ top: スクロールバック先頭に固定
   └─ pin: 特定位置にピン留め

4. ページ削除
   └─ トラッキングピンの更新
   └─ ページメモリの解放
   └─ page_serial_minの更新

5. クリーンアップ
   └─ 全ページのメモリ解放
   └─ プールのリセット
```

### フローチャート

```mermaid
flowchart TD
    A[PageList.init] --> B[MemoryPool.init]
    B --> C[initPages]
    C --> D[viewport_pin作成]
    D --> E[PageList使用可能]

    E --> F{操作種別}
    F -->|行追加| G{ページに空きあり?}
    G -->|Yes| H[行を追加]
    G -->|No| I[新規ページ作成]
    I --> J{最大サイズ超過?}
    J -->|Yes| K[古いページ削除]
    J -->|No| H

    F -->|スクロール| L{方向}
    L -->|上| M[ビューポートを上に移動]
    L -->|下| N[ビューポートを下に移動]

    F -->|リセット| O[全ページクリア]
    O --> P[初期状態に戻す]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | 最小サイズ保証 | アクティブ領域+最低2ページ分は常に確保 | max_size設定時 |
| BR-002 | ページ再利用 | 削除されたページはプールに戻して再利用 | ページ削除時 |
| BR-003 | シリアル番号管理 | 各ページに一意のシリアル番号を付与して有効性を追跡 | ページ作成時 |
| BR-004 | ピン自動更新 | トラッキングピンはページ削除時に自動更新 | ページ削除時 |
| BR-005 | 標準容量優先 | 可能な限り標準サイズページを使用 | ページ作成時 |

### 計算ロジック

- 最小max_size: `minMaxSize(cols, rows)` = 標準ページサイズ * (必要ページ数 + 1)
- 標準ページサイズ: `std_size` = Page.layout(std_capacity).total_size
- ページプリヒート数: `page_preheat` = 4

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

本機能はデータベースを使用しない。すべてのデータはメモリ内で管理される。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| E-001 | メモリ不足 | ページ確保時のメモリ不足 | 古いスクロールバックを削除して再試行 |
| E-002 | ピントラッキング失敗 | ピンのトラッキング登録失敗 | エラーを呼び出し元に伝播 |

### リトライ仕様

メモリ不足時は古いスクロールバックページを削除してリトライする。

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

トランザクション処理は使用しない。各操作は即座に適用される。

## パフォーマンス要件

- ページ作成/削除: O(1)（プールからの確保/返却）
- ビューポート移動: O(1)
- ピンによる位置参照: O(1)
- メモリ効率: ページプールによる断片化防止

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

- max_size設定によるメモリ消費制限
- ページメモリは0初期化（機密データの漏洩防止）

## 備考

- macOSではタグ付きページアロケータを使用してメモリ使用量を追跡可能
- 標準ページサイズを超える列数の場合は非標準ページを使用

---

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

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

### 推奨読解順序

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

PageListの基本的なデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | PageList.zig | `src/terminal/PageList.zig` | List、Node、MemoryPool、Viewport定義 |
| 1-2 | page.zig | `src/terminal/page.zig` | Page、Capacity、Layout構造体 |
| 1-3 | PageList.zig | `src/terminal/PageList.zig` | Pin、PinSet定義 |

**読解のコツ**: `List`は`IntrusiveDoublyLinkedList(Node)`で、各`Node`は`Page`データとシリアル番号を持つ。`MemoryPool`はノード、ページ、ピンの3種類のプールを管理。

#### Step 2: 初期化処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | PageList.zig | `src/terminal/PageList.zig` | init()関数 - 358-418行目 |
| 2-2 | PageList.zig | `src/terminal/PageList.zig` | initPages()関数 - 426-499行目 |
| 2-3 | PageList.zig | `src/terminal/PageList.zig` | MemoryPool.init() - 85-102行目 |

**主要処理フロー**:
- **358-418行目**: `init()` - MemoryPool初期化、初期ページ作成、ビューポートピン作成
- **426-499行目**: `initPages()` - 初期ビューポート分のページを作成
- **370-374行目**: `MemoryPool.init()` - ノード、ページ、ピンプールの初期化

#### Step 3: ページ管理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | PageList.zig | `src/terminal/PageList.zig` | initialCapacity()関数 - 284-318行目 |
| 3-2 | PageList.zig | `src/terminal/PageList.zig` | minMaxSize()関数 - 233-274行目 |
| 3-3 | PageList.zig | `src/terminal/PageList.zig` | pageAllocator()関数 - 320-333行目 |

**主要処理フロー**:
- **284-318行目**: `initialCapacity()` - 列数に基づく初期容量計算
- **233-274行目**: `minMaxSize()` - 最小必要サイズの計算
- **320-333行目**: `pageAllocator()` - プラットフォーム別ページアロケータ

#### Step 4: ビューポート管理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | PageList.zig | `src/terminal/PageList.zig` | Viewport union - 207-221行目 |
| 4-2 | PageList.zig | `src/terminal/PageList.zig` | viewport_pin、viewport_pin_row_offset |

**主要処理フロー**:
- **207-221行目**: `Viewport` - active、top、pinの3モード
- **175-176行目**: `viewport_pin` - ピン位置のビューポート用トラッキングピン

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

```
PageList
    |
    +-- init()
    |      |
    |      +-- MemoryPool.init()
    |      |      +-- NodePool.initPreheated()
    |      |      +-- PagePool.initPreheated()
    |      |      +-- PinPool.initPreheated()
    |      |
    |      +-- initPages()
    |      |      +-- initialCapacity()
    |      |      +-- Page.layout()
    |      |      +-- pool.nodes.create()
    |      |      +-- pool.pages.create()
    |      |
    |      +-- pool.pins.create()  [viewport_pin]
    |      +-- tracked_pins.putNoClobber()
    |
    +-- trackPin()
    |      +-- pool.pins.create()
    |      +-- tracked_pins.putNoClobber()
    |
    +-- untrackPin()
    |      +-- tracked_pins.remove()
    |      +-- pool.pins.destroy()
    |
    +-- deinit()
           +-- MemoryPool.deinit()
```

### データフロー図

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

初期化パラメータ ──────> PageList.init()
   (cols, rows,              |
    max_size)                v
                    MemoryPool初期化 ─────> pool
                             |
                             v
                    initPages() ──────────> pages (List)
                             |
                             v
                    viewport_pin作成 ─────> viewport_pin

新規行データ ──────────> grow() / scroll()
                             |
                             v
                    ページ追加/削除 ─────> 更新されたpages
                             |
                             v
                    max_size制限確認 ────> 古いページ削除

スクロール要求 ─────────> viewport移動
                             |
                             v
                    viewport.active/top/pin ─> レンダラー
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| PageList.zig | `src/terminal/PageList.zig` | ソース | ページリスト管理のメインロジック |
| page.zig | `src/terminal/page.zig` | ソース | ページ、行、セルのデータ構造 |
| point.zig | `src/terminal/point.zig` | ソース | ポイント（座標）定義 |
| size.zig | `src/terminal/size.zig` | ソース | サイズ関連の型定義 |
| Screen.zig | `src/terminal/Screen.zig` | ソース | PageListの使用元 |
| highlight.zig | `src/terminal/highlight.zig` | ソース | ハイライト処理 |
