# 画面設計書 8-RadioMenu（単一選択メニュー）

## 概要

選択肢リストから1つを選択するターミナルUIコンポーネントの設計書。ページング・キーバインドショートカットに対応する。

### 本画面の処理概要

本画面は、ターミナル上で選択肢リストから1つの項目を選択するためのUIコンポーネントである。`REPL.TerminalMenus` サブモジュールの `RadioMenu` 型として実装されている。

**業務上の目的・背景**：Juliaのプログラムやスクリプト内で、ユーザーに対して選択肢を提示し、1つの回答を得るための標準的なUIコンポーネントを提供する。パッケージのインストーラ、設定ウィザード、対話的なスクリプトなどで使用される。ページング機能により大量の選択肢にも対応し、キーバインドショートカットによる高速選択も可能である。

**画面へのアクセス方法**：プログラムから `request(RadioMenu(options))` または `request("メッセージ", RadioMenu(options))` として呼び出す。REPLモードとは独立した、プログラムから起動するUIコンポーネントである。

**主要な操作・処理内容**：
1. `RadioMenu(options; pagesize=10, keybindings=[], charset=:ascii)` でメニューオブジェクトを作成する
2. `request(menu)` または `request(msg, menu)` でメニューを表示し、ユーザー入力を待つ
3. 上下矢印キーで選択候補を移動する（ページングありの場合はスクロール）
4. PageUp/PageDown でページ単位の移動ができる
5. Home/Endキーで先頭/末尾にジャンプできる
6. Enterキーで現在のカーソル位置の項目を選択（確定）する
7. `q` キーまたは `Ctrl+C` でキャンセルする
8. キーバインドショートカット（keybindings）が設定されている場合、対応する文字キーで直接選択できる

**画面遷移**：プログラムから呼び出され、選択完了またはキャンセル後にプログラムに制御が戻る。独立したUIコンポーネントであり、REPLモード遷移には含まれない。

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

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 102 | REPL | 主機能 | REPL.TerminalMenusサブモジュール内のRadioMenu型として実装。request()で呼び出し |
| 36 | テキスト出力 | 主機能 | print/IOBufferによるメニュー選択肢のターミナル描画。ページング・カーソル表示を含む |
| 35 | IOストリーム | 補助機能 | IOBuffer/IOContextを使用したメニュー描画バッファの構築 |
| 15 | Array（多次元配列） | 補助機能 | options::Array{String,1}による選択肢リストの管理 |

## 画面種別

単一選択メニュー（ターミナルUIコンポーネント）

## URL/ルーティング

該当なし（ターミナルベースのUIコンポーネント）

## 入出力項目

| 項目名 | 入出力 | 型 | 説明 |
|--------|--------|-----|------|
| options | 入力 | Vector{String} | 選択肢のリスト |
| pagesize | 入力 | Int | 一度に表示する選択肢の数（デフォルト: 10）。-1で自動 |
| keybindings | 入力 | Vector{Char} | 各選択肢に対応するショートカットキー |
| charset | 入力 | Symbol | UIの文字セット（:ascii または :unicode） |
| cursor | 入力/出力 | Int/RefValue{Int} | カーソルの初期位置と最終位置 |
| 戻り値 | 出力 | Int | 選択された項目のインデックス。キャンセル時は -1 |

## 表示項目

| 項目名 | 表示内容 | 条件 |
|--------|----------|------|
| カーソルインジケータ | `>` (ascii) または `→` (unicode) | カーソル位置の項目 |
| 上スクロールインジケータ | `^` (ascii) または `↑` (unicode) | ページ上部にスクロール可能な項目がある場合 |
| 下スクロールインジケータ | `v` (ascii) または `↓` (unicode) | ページ下部にスクロール可能な項目がある場合 |
| 上下スクロールインジケータ | `I` (ascii) または `↕` (unicode) | 上下両方にスクロール可能な場合 |
| 選択肢テキスト | options配列の各要素。改行は `\n` に置換 | 常に表示 |

## イベント仕様

### 1-カーソル移動（上下矢印キー）

上矢印で `move_up!()`、下矢印で `move_down!()` が呼ばれる。カーソル位置を1つ移動し、ページ端に達した場合はスクロールする。`scroll_wrap` がtrueの場合、先頭/末尾でラップアラウンドする。

### 2-ページ移動（PageUp/PageDown）

`page_up!()` / `page_down!()` で一画面分のカーソル移動とスクロールが行われる。

### 3-選択確定（Enter）

Enterキー押下で `pick(menu, cursor)` が呼ばれ、`menu.selected = cursor` が設定される。`pick()` が `true` を返すため `request()` のループを抜ける。

### 4-キャンセル（q / Ctrl+C）

`q` キーまたは `Ctrl+C` で `cancel(menu)` が呼ばれ、`menu.selected = -1` が設定される。`ctrl_c_interrupt` がtrueの場合、Ctrl+Cで `InterruptException` がスローされる。

### 5-キーバインドショートカット

keybindingsが設定されている場合、`keypress()` で対応するキーを検出し、そのインデックスの項目を即座に選択する。

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

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

該当なし（データベースを使用しない）

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| - | 情報 | request()のmsg引数で指定されたメッセージ | request(msg, menu)形式で呼び出した場合 |

## 例外処理

| 例外 | 発生条件 | 対応 |
|------|----------|------|
| ArgumentError | optionsが空の場合 | `RadioMenu must have at least one option` エラー |
| ArgumentError | pagesizeが0以下の場合 | `pagesize must be >= 1` エラー |
| ArgumentError | keybindingsの数がoptionsと不一致（0でも全数でもない場合） | エラーメッセージ表示 |
| InterruptException | Ctrl+C押下（ctrl_c_interrupt=true） | InterruptExceptionをスロー |
| raw modeエラー | 端末がraw modeに入れない場合 | 警告ログを出力 |

## 備考

- `RadioMenu` は `_ConfiguredMenu{C}` を継承し、`AbstractMenu` の実装インタフェースに準拠する
- `pagesize=-1` で自動ページング（全選択肢を1ページに表示）
- カーソルは非表示（`\x1b[?25l`）にされ、終了時に復元（`\x1b[?25h`）される
- 端末のraw modeが有効にされ、キー入力を即座に取得する
- レガシーインタフェース（`writeLine`）とモダンインタフェース（`writeline`）の両方をサポート
- Config構造体（`cursor`, `up_arrow`, `down_arrow`, `updown_arrow`, `scroll_wrap`, `ctrl_c_interrupt`）でUI設定を管理

---

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

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

### 推奨読解順序

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

RadioMenuの中核データ構造はRadioMenu構造体とConfig構造体である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | RadioMenu.jl | `stdlib/REPL/src/TerminalMenus/RadioMenu.jl` | RadioMenu構造体（22-29行）: options, keybindings, pagesize, pageoffset, selected, config |
| 1-2 | config.jl | `stdlib/REPL/src/TerminalMenus/config.jl` | Config構造体（5-12行）: cursor, up_arrow, down_arrow, updown_arrow, scroll_wrap, ctrl_c_interrupt |
| 1-3 | AbstractMenu.jl | `stdlib/REPL/src/TerminalMenus/AbstractMenu.jl` | AbstractMenu抽象型（52行）と_ConfiguredMenu（69行） |

**読解のコツ**: RadioMenuは_ConfiguredMenu{C}を継承しており、Config型パラメータCにより新旧インタフェースを区別する。Configがstruct Configの場合はモダンインタフェース、Dict型の場合はレガシーインタフェース。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | AbstractMenu.jl | `stdlib/REPL/src/TerminalMenus/AbstractMenu.jl` | request()関数（181-247行）: メニュー表示とユーザー入力ループのメインロジック |
| 2-2 | RadioMenu.jl | `stdlib/REPL/src/TerminalMenus/RadioMenu.jl` | コンストラクタ（53-73行）: 引数バリデーションとページサイズ調整 |

**主要処理フロー**:
1. **181-184行**: cursor引数のRef化
2. **188行**: printmenu()で初期表示
3. **191-197行**: raw mode有効化
4. **199行**: カーソル非表示
5. **202-237行**: メインループ（キー入力読み取り → 移動/選択/キャンセル → 再描画）
6. **238-243行**: finally節でraw mode復元とカーソル再表示

#### Step 3: メニュー操作の実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | RadioMenu.jl | `stdlib/REPL/src/TerminalMenus/RadioMenu.jl` | pick()（85-88行）、cancel()（83行）、writeline()（90-92行）、keypress()（94-100行） |
| 3-2 | AbstractMenu.jl | `stdlib/REPL/src/TerminalMenus/AbstractMenu.jl` | move_up!()（263-275行）、move_down!()（277-290行）、page_up!()（292-298行） |

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

```
ユーザープログラム
    |
    +-- RadioMenu(options; pagesize, keybindings, charset)  [53行]
    |
    +-- request(menu) / request(msg, menu)                  [179行]
            |
            +-- printmenu(out, menu, cursor; init=true)     [188行]
            |
            +-- Terminals.raw!(term, true)                  [192行]
            |
            +-- メインループ                                 [202-237行]
            |   +-- readkey(term.in_stream)
            |   +-- move_up!() / move_down!()
            |   +-- page_up!() / page_down!()
            |   +-- pick(menu, cursor)                      [85行]
            |   +-- cancel(menu)                            [83行]
            |   +-- keypress(menu, key)                     [94行]
            |   +-- printmenu(out, menu, cursor)
            |
            +-- Terminals.raw!(term, false)                 [240行]
            +-- selected(menu)                              [246行]
```

### データフロー図

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

options          -----> RadioMenu() 構築          -----> RadioMenu オブジェクト
                        |
request() 呼出  -----> printmenu() 初期描画      -----> ターミナル表示
                        |
キー入力         -----> readkey()
                        |
                  move_up!/move_down!/pick/cancel
                        |
                  printmenu() 再描画             -----> ターミナル更新
                        |
Enter            -----> pick() → selected = cursor
                        |
                  selected(menu)                 -----> Int (選択インデックス)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| RadioMenu.jl | `stdlib/REPL/src/TerminalMenus/RadioMenu.jl` | ソース | RadioMenu型定義。pick(), cancel(), writeline(), keypress() |
| AbstractMenu.jl | `stdlib/REPL/src/TerminalMenus/AbstractMenu.jl` | ソース | request(), move_up!(), move_down!(), printmenu() |
| config.jl | `stdlib/REPL/src/TerminalMenus/config.jl` | ソース | Config, MultiSelectConfig構造体 |
| TerminalMenus.jl | `stdlib/REPL/src/TerminalMenus/TerminalMenus.jl` | ソース | モジュール定義、export |
| util.jl | `stdlib/REPL/src/TerminalMenus/util.jl` | ソース | readkey()等のユーティリティ関数 |
