# 画面設計書 9-MultiSelectMenu（複数選択メニュー）

## 概要

選択肢リストから複数を選択するターミナルUIコンポーネントの設計書。全選択(a)、全解除(n)、確定(d)、中断(q)のキー操作を持つ。

### 本画面の処理概要

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

**業務上の目的・背景**：Juliaのプログラムやスクリプト内で、ユーザーに対して複数の選択肢を提示し、チェックボックス形式で複数の回答を得るための標準的なUIコンポーネントを提供する。パッケージの一括インストール、テスト項目の選択、設定オプションの複数選択などで使用される。RadioMenuとは異なり、Enterキーは選択のトグルとして使用され、`d` キーで最終確定する。

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

**主要な操作・処理内容**：
1. `MultiSelectMenu(options; pagesize=10, selected=[], charset=:ascii)` でメニューオブジェクトを作成する
2. `request(menu)` でメニューを表示し、ユーザー入力を待つ
3. 上下矢印キーで選択候補を移動する
4. Enterキーで現在のカーソル位置の項目の選択をトグル（選択/解除）する
5. `a` / `A` キーで全選択する
6. `n` / `N` キーで全解除する
7. `d` / `D` キーで確定する（選択結果を返す）
8. `q` キーまたは `Ctrl+C` でキャンセルする（空のSetを返す）

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

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

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 102 | REPL | 主機能 | REPL.TerminalMenusサブモジュール内のMultiSelectMenu型として実装。request()で呼び出し |
| 36 | テキスト出力 | 主機能 | print/IOBufferによるメニュー選択肢のターミナル描画。チェックボックス表示を含む |
| 24 | 集合（Set） | 補助機能 | Set{Int}による選択済みアイテムの管理。Enterでトグル、a=全選択、n=全解除 |
| 35 | IOストリーム | 補助機能 | IOBuffer/IOContextを使用したメニュー描画バッファの構築 |
| 15 | Array（多次元配列） | 補助機能 | options::Array{String,1}による選択肢リストの管理 |

## 画面種別

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

## URL/ルーティング

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

## 入出力項目

| 項目名 | 入出力 | 型 | 説明 |
|--------|--------|-----|------|
| options | 入力 | Vector{String} | 選択肢のリスト |
| pagesize | 入力 | Int | 一度に表示する選択肢の数（デフォルト: 10）。-1で自動 |
| selected | 入力 | Vector{Int} | 初期選択済みアイテムのインデックスリスト |
| charset | 入力 | Symbol | UIの文字セット（:ascii または :unicode） |
| 戻り値 | 出力 | Set{Int} | 選択された項目のインデックスの集合。キャンセル時は空のSet |

## 表示項目

| 項目名 | 表示内容 | 条件 |
|--------|----------|------|
| ヘッダー | `[press: Enter=toggle, a=all, n=none, d=done, q=abort]` | 常に表示 |
| カーソルインジケータ | `>` (ascii) または `→` (unicode) | カーソル位置の項目 |
| チェック済みマーク | `[X]` (ascii) または (unicode) | 選択済みの項目 |
| 未チェックマーク | `[ ]` (ascii) または (unicode) | 未選択の項目 |
| 上下スクロールインジケータ | RadioMenuと同様 | ページング時 |
| 選択肢テキスト | options配列の各要素。改行は `\n` に置換 | 常に表示 |

## イベント仕様

### 1-選択トグル（Enter）

Enterキー押下で `pick(menu, cursor)` が呼ばれる。カーソル位置のインデックスが `menu.selected` Setに含まれていれば削除（`delete!`）し、含まれていなければ追加（`push!`）する。`pick()` は `false` を返すため、メニューは終了しない。

### 2-全選択（a/A）

`keypress()` で `a` / `A` キーを検出し、`menu.selected = Set(1:length(menu.options))` で全項目を選択状態にする。

### 3-全解除（n/N）

`keypress()` で `n` / `N` キーを検出し、`menu.selected = Set{Int}()` で全項目を未選択にする。

### 4-確定（d/D）

`keypress()` で `d` / `D` キーを検出し、`true` を返すことでメニューループを終了する。

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

`cancel(menu)` により `menu.selected = Set{Int}()` に設定される（空のSet）。

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

RadioMenuと同様に `move_up!()` / `move_down!()` で移動する。

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

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

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

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| - | 情報 | `[press: Enter=toggle, a=all, n=none, d=done, q=abort]` | ヘッダーとして常に表示 |
| - | 情報 | request()のmsg引数で指定されたメッセージ | request(msg, menu)形式で呼び出した場合 |

## 例外処理

| 例外 | 発生条件 | 対応 |
|------|----------|------|
| ArgumentError | optionsが空の場合 | `MultiSelectMenu must have at least one option` エラー |
| ArgumentError | pagesizeが0以下の場合 | `pagesize must be >= 1` エラー |
| InterruptException | Ctrl+C押下（ctrl_c_interrupt=true） | InterruptExceptionをスロー |

## 備考

- `MultiSelectMenu` は `_ConfiguredMenu{C}` を継承する（RadioMenuと同じ継承階層）
- `MultiSelectConfig` はConfig + checked/unchecked文字列を持つ複合構成
- `header()` メソッドが操作ヒントを返す
- `selected` 引数で初期選択状態を指定可能（Julia 1.6以降）
- Enterの動作がRadioMenu（確定）とMultiSelectMenu（トグル）で異なる点に注意

---

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

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

### 推奨読解順序

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

MultiSelectMenuの中核データ構造はMultiSelectMenu構造体とMultiSelectConfig構造体である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | MultiSelectMenu.jl | `stdlib/REPL/src/TerminalMenus/MultiSelectMenu.jl` | MultiSelectMenu構造体（30-36行）: options, pagesize, pageoffset, selected(Set{Int}), config |
| 1-2 | config.jl | `stdlib/REPL/src/TerminalMenus/config.jl` | MultiSelectConfig構造体（14-18行）: config(Config), checked, unchecked |

**読解のコツ**: MultiSelectMenuの `selected` フィールドはSet{Int}型であり、RadioMenuのInt型と異なる。これが複数選択を可能にしている。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | MultiSelectMenu.jl | `stdlib/REPL/src/TerminalMenus/MultiSelectMenu.jl` | コンストラクタ（58-81行）: 初期選択状態の設定とMultiSelectConfigの構築 |
| 2-2 | AbstractMenu.jl | `stdlib/REPL/src/TerminalMenus/AbstractMenu.jl` | request()関数（181-247行）: RadioMenuと共通のメニュー表示・入力ループ |

**主要処理フロー**:
1. **58-66行**: 引数バリデーションとページサイズ調整
2. **69-72行**: selected引数からSet{Int}を構築
3. **89行**: header()が操作ヒント文字列を返す
4. **96-104行**: pick()でSet{Int}に対してトグル（追加/削除）

#### Step 3: キー操作の実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | MultiSelectMenu.jl | `stdlib/REPL/src/TerminalMenus/MultiSelectMenu.jl` | pick()（96-104行）、cancel()（93行）、writeline()（106-114行）、keypress()（119-128行） |

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

```
ユーザープログラム
    |
    +-- MultiSelectMenu(options; pagesize, selected, charset)  [58行]
    |
    +-- request(menu) / request(msg, menu)
            |
            +-- printmenu() 初期描画
            |       +-- header() → "[press: Enter=toggle, a=all, n=none, d=done, q=abort]"
            |       +-- writeline() → チェックボックス + テキスト
            |
            +-- メインループ
            |   +-- readkey()
            |   +-- Enter → pick(menu, cursor)  [96行]
            |   |       +-- toggle: push!/delete! on selected::Set{Int}
            |   +-- d/D → keypress() returns true → ループ終了
            |   +-- a/A → keypress() → selected = Set(1:length(options))
            |   +-- n/N → keypress() → selected = Set{Int}()
            |   +-- q → cancel() → selected = Set{Int}()
            |
            +-- selected(menu)  →  Set{Int}
```

### データフロー図

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

options          -----> MultiSelectMenu() 構築    -----> MultiSelectMenu オブジェクト
selected(初期)   -----> Set{Int} に変換

request() 呼出  -----> printmenu() 初期描画      -----> ターミナル表示
                        |                                (ヘッダー + チェックボックス)

Enter            -----> pick() トグル             -----> selected Set更新
a/A              -----> 全選択                    -----> selected = Set(1:N)
n/N              -----> 全解除                    -----> selected = Set{Int}()
d/D              -----> 確定                      -----> Set{Int} (選択結果)
q/Ctrl+C         -----> キャンセル                -----> Set{Int}() (空Set)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| MultiSelectMenu.jl | `stdlib/REPL/src/TerminalMenus/MultiSelectMenu.jl` | ソース | MultiSelectMenu型定義。pick(), cancel(), writeline(), keypress(), header() |
| 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 |
