# 画面設計書 6-履歴検索モード

## 概要

インクリメンタルにREPL履歴を検索するインターフェースの設計書。入力に応じてフィルタリングされた候補をリアルタイムで表示する。

### 本画面の処理概要

本画面は、Julia REPLの履歴検索モードであり、ユーザーが入力したクエリに基づいてREPLの入力履歴をインクリメンタルにフィルタリングし、マッチした候補をリアルタイムで表示する。

**業務上の目的・背景**：過去にREPLで入力したコマンドや式を効率的に再利用するためのインタフェースである。インクリメンタル検索により、クエリの入力に応じて候補が即座に絞り込まれるため、大量の履歴から目的のエントリを素早く見つけることができる。複数エントリの選択、クリップボードへのコピー、ファイルへの保存にも対応する。タスクベースの並行処理（プロンプトタスクとディスプレイタスク）により、入力とフィルタリング・表示が非同期に処理される。

**画面へのアクセス方法**：Juliaプロンプト画面で `Ctrl+R` キーを押下すると本モードに起動する。

**主要な操作・処理内容**：
1. `Ctrl+R` で履歴検索モードが起動し、プロンプトタスクとディスプレイタスクが@spawnで並行起動される
2. ユーザーが検索クエリを入力すると、`ConditionSet` に基づくフィルタリングが実行される
3. フィルタリング結果がリアルタイムで表示され、マッチ箇所がハイライトされる
4. 上下キーまたはPageUp/PageDownでホバーカーソルを移動できる
5. Tabキーで候補の選択/解除をトグルできる（複数選択対応）
6. Enterで確定し、選択した履歴エントリがREPLバッファに挿入される
7. `Ctrl+C` / `Ctrl+D` で中断し、Juliaプロンプトに戻る
8. 選択した履歴のクリップボードコピーやファイル保存も可能

**画面遷移**：
- 遷移元：Juliaプロンプト（`Ctrl+R`）
- 遷移先：確定時 - 選択した履歴のモード（julia/help/shell/pkg）に応じたプロンプト
- 中断時：Juliaプロンプト（`Ctrl+C` / `Ctrl+D`）

**権限による表示制御**：権限による表示制御は存在しない。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 102 | REPL | 主機能 | REPL.History.runsearch()によるインクリメンタル履歴検索インターフェースの実装 |
| 30 | 文字列検索・置換 | 主機能 | contains/occursinベースのフィルタリングによる入力パターンに一致する履歴エントリの検索 |
| 48 | Channel | 補助機能 | Channel{Symbol}によるプロンプトタスクとディスプレイタスク間のイベント通信 |
| 46 | Task（コルーチン） | 補助機能 | @spawnによるプロンプトタスク・ディスプレイタスクの並行実行 |
| 49 | 同期プリミティブ | 補助機能 | @lockによるイベントチャネルへの排他的アクセス制御 |
| 37 | ファイルI/O | 補助機能 | 履歴ファイル（.julia_history）の読み込みによる履歴エントリの取得 |
| 36 | テキスト出力 | 補助機能 | ANSIエスケープシーケンスによるフィルタ結果のリアルタイム表示・ハイライト |

## 画面種別

インクリメンタル検索（履歴検索モード）

## URL/ルーティング

該当なし（ターミナルベースのCLIアプリケーション）

## 入出力項目

| 項目名 | 入出力 | 型 | 説明 |
|--------|--------|-----|------|
| 検索クエリ | 入力 | String | 履歴を検索するための文字列 |
| フィルタ結果 | 出力 | Vector{HistEntry} | フィルタリングされた履歴エントリのリスト |
| 選択結果 | 出力 | NamedTuple{(:mode, :text)} | 確定時の選択モードとテキスト |

## 表示項目

| 項目名 | 表示内容 | 条件 |
|--------|----------|------|
| 検索プロンプト | `history>` スタイルのプロンプト | 常に表示 |
| 検索クエリ | ユーザーが入力した検索文字列 | 常に表示 |
| 候補リスト | フィルタリングされた履歴エントリ一覧（マッチ箇所ハイライト付き） | フィルタ結果が存在する場合 |
| ホバーカーソル | 現在選択位置のインジケータ | 候補が存在する場合 |
| 選択済みエントリ | Tabで選択したエントリの区別表示 | 複数選択時 |
| 結果カウント | 現在の候補数 | フィルタリング時 |

## イベント仕様

### 1-クエリ入力（文字入力）

ユーザーが文字を入力するたびに `:edit` イベントがChannel経由でディスプレイタスクに送信される。ディスプレイタスクは `ConditionSet` を構築し、`filterchunkrev!()` でフィルタリングを実行する。フィルタリングは時間制限付き（maxtime=0.01秒）のチャンク処理で行われ、大量の履歴でもUIの応答性が維持される。

### 2-カーソル移動（上下キー / PageUp/Down）

上下キーで1行、PageUp/Downでページ単位のホバーカーソル移動。`movehover()` 関数で新しいカーソル位置とスクロール位置が計算される。

### 3-選択トグル（Tab）

Tabキーで現在ホバー中のエントリの選択状態をトグル。`toggleselection()` で `SelectorState.selection` が更新される。

### 4-確定（Enter）

`:confirm` イベントにより検索を確定。`fullselection()` で選択されたエントリのテキストとモードが結合され、対応するREPLモードのバッファに挿入される。

### 5-中断（Ctrl+C / Ctrl+D）

`:abort` イベントにより検索を中断。空の結果が返され、Juliaプロンプトに戻る。

### 6-クリップボードコピー

`:copy` イベントにより選択内容をクリップボードにコピー。`saveclipboard()` で `clipboard()` 関数を使用。

### 7-ファイル保存

`:filesave` イベントにより選択内容をファイルに保存。`savefile()` でユーザーにファイルパスを入力させ、指定ファイルに書き込む。

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

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

該当なし（データベースを使用しない。履歴ファイルの読み取りのみ）

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| - | 情報 | `history> Copied N lines to clipboard` | クリップボードコピー時 |
| - | 情報 | `history> Wrote N selected lines to {filename}` | ファイル保存成功時 |
| - | 情報 | `history> History selection aborted` | ファイル保存中断時 |

## 例外処理

| 例外 | 発生条件 | 対応 |
|------|----------|------|
| InterruptException | Ctrl+C押下 | 検索を中断しJuliaプロンプトに戻る |
| 履歴ファイル読込エラー | 履歴ファイルが破損・アクセス不可 | エラーを無視して空の履歴で動作 |

## 備考

- `runsearch()` が履歴検索のメインエントリポイント
- プロンプトタスク（`runprompt!`）とディスプレイタスク（`run_display!`）が `@spawn` で並行起動される
- `Channel{Symbol}(Inf)` で無制限バッファのイベントチャネルが使用される
- フィルタリングは `filterchunkrev!()` で時間制限付きチャンク処理（maxtime=0.01秒）
- 候補キャッシュは `addcache!()` による指数減衰型のログ構造キャッシュ
- `SelectorState` が検索UI全体の状態（クエリ、フィルタ、候補、スクロール位置、選択）を保持する
- ヘルプ表示は検索クエリに特定の文字列を入力することで表示される
- `StyledStrings` と `JuliaSyntaxHighlighting` によるリッチな表示

---

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

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

### 推奨読解順序

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

履歴検索の中核データ構造はHistEntry、SelectorState、FilterSpecである。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | histfile.jl | `stdlib/REPL/src/History/histfile.jl` | HistEntry構造体: mode, date, content, index フィールドを確認 |
| 1-2 | display.jl | `stdlib/REPL/src/History/display.jl` | SelectorState構造体: area, query, filter, candidates, scroll, selection, hover |
| 1-3 | resumablefiltering.jl | `stdlib/REPL/src/History/resumablefiltering.jl` | FilterSpec, ConditionSet: フィルタリング条件の構造 |

**読解のコツ**: SelectorStateは不変（immutable）な状態オブジェクトとして設計されており、状態更新の度に新しいインスタンスが生成される。これにより前状態との差分検出と効率的な再描画が可能になっている。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | search.jl | `stdlib/REPL/src/History/search.jl` | runsearch()関数（11-19行）: プロンプトタスクとディスプレイタスクの起動 |

**主要処理フロー**:
1. **12行**: `update!(histfile)` で履歴ファイルを最新状態に更新
2. **13行**: `Channel{Symbol}(Inf)` でイベントチャネルを作成
3. **14行**: `create_prompt()` でプロンプト仕様を作成
4. **15行**: `@spawn runprompt!()` でプロンプトタスクを起動
5. **16行**: `@spawn run_display!()` でディスプレイタスクを起動
6. **17-18行**: プロンプトタスクの完了を待ち、ディスプレイタスクの結果を取得

#### Step 3: ディスプレイタスクのイベントループを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | search.jl | `stdlib/REPL/src/History/search.jl` | run_display!()関数（49-216行）: イベントループとフィルタリング処理 |
| 3-2 | search.jl | `stdlib/REPL/src/History/search.jl` | movehover()関数（244-279行）: カーソル移動ロジック |
| 3-3 | search.jl | `stdlib/REPL/src/History/search.jl` | toggleselection()関数（286-313行）: 選択トグルロジック |
| 3-4 | search.jl | `stdlib/REPL/src/History/search.jl` | fullselection()関数（26-39行）: 最終選択結果の構築 |

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

```
runsearch(histfile, term)                [11行]
    |
    +-- update!(histfile)                [12行]
    +-- create_prompt(events, term)      [14行]
    |
    +-- @spawn runprompt!(pspec, events) [15行]
    |       +-- キー入力処理
    |       +-- events に :edit/:up/:down/:confirm/:abort 等を送信
    |
    +-- @spawn run_display!(pspec, events, records) [16行]
            +-- イベントループ           [67-216行]
            |   +-- :abort → 空結果を返す
            |   +-- :confirm → 選択結果を返す
            |   +-- :edit → ConditionSet構築 → filterchunkrev!()
            |   +-- :up/:down → movehover()
            |   +-- :tab → toggleselection()
            |   +-- :copy → saveclipboard()
            |   +-- :filesave → savefile()
            |
            +-- redisplay_all()          (表示更新)
```

### データフロー図

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

Ctrl+R           -----> runsearch() 起動          -----> 検索UI表示
                        |
キー入力         -----> runprompt!() (プロンプトタスク)
                        |
                  events Channel{Symbol}
                        |
                  run_display!() (ディスプレイタスク)
                        |
                  ConditionSet 構築
                  filterchunkrev!()
                        |
                  SelectorState 更新
                  redisplay_all()           -----> フィルタ結果表示
                        |
Enter            -----> fullselection()     -----> (mode, text) 結果
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| History.jl | `stdlib/REPL/src/History/History.jl` | ソース | Historyモジュール定義。サブモジュールのinclude |
| search.jl | `stdlib/REPL/src/History/search.jl` | ソース | runsearch(), run_display!(), movehover(), toggleselection(), fullselection() |
| histfile.jl | `stdlib/REPL/src/History/histfile.jl` | ソース | HistEntry, HistoryFile構造体。履歴ファイルの読み書き |
| prompt.jl | `stdlib/REPL/src/History/prompt.jl` | ソース | create_prompt(), runprompt!() |
| display.jl | `stdlib/REPL/src/History/display.jl` | ソース | SelectorState, redisplay_all() |
| resumablefiltering.jl | `stdlib/REPL/src/History/resumablefiltering.jl` | ソース | FilterSpec, ConditionSet, filterchunkrev!() |
| REPL.jl | `stdlib/REPL/src/REPL.jl` | ソース | histsearch()からrunsearch()への接続（105行） |
