# エディタと入力ハンドリング — USBキーボード・PCシリアル・ファイル選択

> withkeyboard.c (TinyUSB ホスト HID キーボードモード) と withoutkeyboard.c (USB シリアル PC 接続モード) の分岐、interface/keyinput.c のキー入力バッファリング、interface/usbkeyboard.c の HID 仮想キーテーブル変換、interface/fileselect.c の SD カードファイルブラウザ、editor.c のインタラクティブ BASIC テキストエディタを解説します。

- Repository: machikania/phyllosoma
- GitHub: https://github.com/machikania/phyllosoma
- Human wiki: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c
- Complete Markdown: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/llms-full.txt

## Source Files

- `withkeyboard.c`
- `withoutkeyboard.c`
- `editor.c`
- `interface/keyinput.c`
- `interface/usbkeyboard.c`
- `interface/fileselect.c`

---

<details>
<summary>関連するソースファイル</summary>
このWikiページの生成に使用したファイル:

- [withkeyboard.c](withkeyboard.c)
- [withoutkeyboard.c](withoutkeyboard.c)
- [editor.c](editor.c)
- [editor.h](editor.h)
- [interface/keyinput.c](interface/keyinput.c)
- [interface/usbkeyboard.c](interface/usbkeyboard.c)
- [interface/usbkeyboard.h](interface/usbkeyboard.h)
- [interface/fileselect.c](interface/fileselect.c)
- [CMakeLists.txt](CMakeLists.txt)
- [pcconnect.c](pcconnect.c)
</details>

# エディタと入力ハンドリング — USBキーボード・PCシリアル・ファイル選択

Phyllosoma は Raspberry Pi Pico 向け MachiKania BASIC 処理系であり、**2種類のファームウェアビルド**を提供します。USB HID キーボードを接続して使う `*_kb.uf2` ビルドと、PC から USB シリアル経由でプログラムを転送する `*.uf2` ビルドです。この分岐は `withkeyboard.c` と `withoutkeyboard.c` を排他的にコンパイルすることで実現されており、どちらのビルドでも共通の BASIC ランタイム API (`lib_readkey`・`lib_inkey`・`lib_input` など) が同じシグネチャで提供されます。キーボードビルドでは `interface/usbkeyboard.c` が TinyUSB ホスト HID ドライバを実装し、`interface/keyinput.c` がそのキーコードバッファを消費してカーソル付きの行入力機能を提供します。SD カードのファイル選択は、キーボードビルドでは `editor.c` 内のキーボード駆動ブラウザが、ゲームパッド（非キーボード）ビルドでは `interface/fileselect.c` の GPIO ボタン駆動ブラウザが担当します。

---

## コンパイル時分岐: `withkeyboard.c` と `withoutkeyboard.c`

CMakeLists.txt は 2 つの独立した実行ファイルを定義します。

| ターゲット | 追加ソース | USB 動作 |
|---|---|---|
| `${CODE_NAME}_kb` | `withkeyboard.c`, `editor.c`, `interface/usbkeyboard.c`, `interface/keyinput.c`, `hid_virtual_keytable.c` | TinyUSB ホスト（HID キーボード）。stdio USB は**無効** |
| `${CODE_NAME}` | `withoutkeyboard.c`, `pcconnect.c` | stdio USB CDC を**有効化**してシリアル通信 |

Sources: [CMakeLists.txt:108-145]()

`withkeyboard.c` は `const char g_active_usb_keyboard=1;` を定義し、`withoutkeyboard.c` は `=0` を定義します。`withoutkeyboard.c` では `vkey`・`lockkey`・`keytype` などのグローバル変数も同ファイルでダミー定義され、`usbkb_mounted()` は常に `false` を返します。

Sources: [withkeyboard.c:25](), [withoutkeyboard.c:25-32]()

### 初期化シーケンス

```text
電源投入
  │
  ├─[キーボードビルド] post_inifile()
  │    usbkb_init() ── TinyUSB ホスト初期化
  │    start_core1()
  │    request_core1_callback(usbk_polling_handler)  ← 10ms 毎
  │    usbkb_mounted() ループで待機 (g_wait_for_keyboard ms)
  │    見つかった → g_usb_peripheral = USB_PERIPHERAL_KEYBOARD
  │    見つからない → g_fileselect_no_keyboard=1
  │
  ├─[シリアルビルド] post_inifile()
  │    connect2pc()  ← "MACHIKAP" ハンドシェイク
  │
  pre_fileselect()
  │    [キーボードビルド] → texteditor() (editor.c) ← 戻らない
  │    [シリアルビルド]   → なにもしない → fileselect() (interface/fileselect.c)
```

Sources: [withkeyboard.c:30-78](), [withoutkeyboard.c:35-40](), [pcconnect.c:107-117]()

---

## `interface/usbkeyboard.c` — TinyUSB HID キーボードドライバ

### データ構造とキューイング

```text
Core1 (usbkb_polling)         Core0 (アプリ)
──────────────────────         ────────────────
tuh_task()                     usbkb_readkey()
usbkb_task()    ──書込→  [keycodebuf: uint16_t[16]]  ──読出→ vkey
usbkbled_task()                keycodeExists() マクロ
                semaphore keycodebuf_sem で保護
```

`keycodebuf` は 16 エントリのリングバッファです。`keycodebufp1`（書き込み先頭）と `keycodebufp2`（読み出し先頭）の 2 つの揮発ポインタで管理されます。セマフォ `keycodebuf_sem` によって Core0/Core1 間の競合を防ぎます。

Sources: [interface/usbkeyboard.c:26-37](), [interface/usbkeyboard.h:1]()

### HID レポート処理パイプライン

```mermaid
sequenceDiagram
    participant USB as TinyUSB (Core1)
    participant CB as tuh_hid_report_received_cb
    participant RPT as process_kbd_report
    participant TASK as usbkb_task
    participant BUF as keycodebuf
    participant APP as usbkb_readkey (Core0)

    USB->>CB: HID割り込みレポート受信
    CB->>RPT: hid_keyboard_report_t
    RPT->>RPT: rollover エラー(keycode[0]==1)なら無視
    RPT-->>TASK: usbkb_report に格納

    Note over TASK: 10ms ポーリングで呼出
    TASK->>TASK: shiftkeycheck(modifier)
    TASK->>TASK: hidkey2virtualkey_jp/en[] で VK 変換
    TASK->>BUF: pushkeycodebuf(vk | shift<<8)
    TASK->>TASK: キーリピート処理
    TASK->>TASK: usbkb_keystatus[] 更新

    APP->>BUF: popkeycodebuf()
    BUF-->>APP: vkey (下位8bit=VK, 上位8bit=修飾)
    APP->>APP: vk2asc1/2_jp/en[] で ASCII 変換
```

Sources: [interface/usbkeyboard.c:71-175](), [interface/usbkeyboard.c:254-280]()

### HID コード → 仮想キーコード → ASCII 変換

`usbkb_task()` は最大 6 同時押しキーのそれぞれを変換テーブル `hidkey2virtualkey_jp[]` または `hidkey2virtualkey_en[]` で Windows 仮想キーコード (`VK_*`) に変換してバッファに積みます。`usbkb_readkey()` がバッファから取り出す際、シフト状態に応じて以下のテーブルで ASCII 変換します。

| モード | シフトなし | シフトあり |
|---|---|---|
| 日本語英数 (`keytype=0`) | `vk2asc1_jp[]` | `vk2asc2_jp[]` |
| 日本語カナ (`CHK_SCRLK`) | `vk2kana1[]` | `vk2kana2[]` |
| 英語 (`keytype=1`) | `vk2asc1_en[]` | `vk2asc2_en[]` |

Ctrl・Alt・Win が押されている場合は ASCII 0 を返し、グローバル `vkey` のみをセットします。キーリピートは初回 500 ms・以降 40 ms 間隔（`KEYREPEAT1`/`KEYREPEAT2`）です。

Sources: [interface/usbkeyboard.h:1-3](), [interface/usbkeyboard.c:195-255]()

---

## `interface/keyinput.c` — キー入力バッファリングとカーソル管理

`keyinput.c` は `usbkb_readkey()` の上位レイヤーで、エディタや BASIC `INPUT` 文から呼び出される高水準キー入力 API を提供します。

### 主要関数

| 関数 | 動作 |
|---|---|
| `inputchar()` | キーが押されるまでブロック。60Hz タイマで 1 フレーム毎に `usbkb_readkey()` をポーリング |
| `cursorinputchar()` | `inputchar()` と同じだが待機中にカーソルを点滅表示 |
| `printinputchar()` | 通常文字が入力されるまで待機し、入力文字を画面に出力 |
| `blinkcursorchar()` | `BLINKTIME` フレームで挿入カーソル（`CURSORCHAR`）と上書きカーソル（`CURSORCHAR2`）を交互表示 |
| `lineinput(s, n)` | フル行入力。挿入/上書きモード・←/→/Home/End/BS/Del に対応 |

`lineinput()` は内部バッファ `lineinputbuf[256]` を使い、初期文字列を事前ロードする機能も持ちます。`VK_RETURN`/`VK_SEPARATOR` で確定、`VK_ESCAPE`/`VK_CANCEL` で -1 を返して中断します。

Sources: [interface/keyinput.c:65-85](), [interface/keyinput.c:115-220]()

---

## `interface/fileselect.c` — SD カードファイルブラウザ（ゲームパッド版）

非キーボードビルドのファイル選択に使用される GPIO ボタン駆動のブラウザです。`shared_files` ライブラリに含まれるため、両ビルドにリンクされますが、キーボードビルドでは実行パスが通りません。

### GPIO ボタン管理

```c
// keystatus2: 今回新たに押されたボタン
// keystatus3: 今回離されたボタン
// keycountXX: 連続押下フレーム数（30超でリピート扱い）
```

`keycheck()` は `gpio_get_all()` で全ボタンを一括取得し、差分を計算します。

Sources: [interface/fileselect.c:38-64]()

### ファイル一覧と操作フロー

`fileselect()` はディレクトリを再帰的に走査し、最大 512 エントリ（`MAXFILE`）をリストアップします。ディレクトリは先頭に配置され、ファイルは `file_sort()` で整列されます。

| ボタン操作 | 動作 |
|---|---|
| UP/DOWN/LEFT/RIGHT | カーソル移動（30フレーム超でリピート） |
| FIRE（短押し） | ファイル選択 / ディレクトリ遷移 |
| START（短押し on ファイル） | `viewfile()` でテキスト内容プレビュー |
| START（長押し 20フレーム超） | タイムスタンプ表示切り替え / 画面回転 |
| FIRE（長押し 20フレーム超） | ソート順サイクル（名前昇順→降順→日付昇順→降順） |

`file_sort()` は選択ソートで実装され、`fnamecmp()` がソートモード (`filesortby` 0–3) に応じたキーを返します。

Sources: [interface/fileselect.c:175-340]()

---

## `editor.c` — インタラクティブ BASIC テキストエディタ

キーボードビルド専用のフルフィーチャーテキストエディタです。`texteditor()` から呼ばれると決して戻らず、BASIC ソースを編集して実行に引き渡します。

### テキストバッファのデータ構造

```mermaid
classDiagram
    class _tbuf {
        +_tbuf *prev
        +_tbuf *next
        +unsigned short n
        +unsigned char Buf[200]
    }
    _tbuf --> _tbuf : linked list

    class TextBuffer {
        <<array>>
        181 nodes (TBUFMAXLINE)
        max 36000 chars total
    }
    TextBuffer o-- _tbuf
```

テキストは `TBUFSIZE=200` バイト単位の `_tbuf` ノードの双方向リンクリストとして保持されます。`TBufstart` がリスト先頭、`cursorbp`/`cursorix` がカーソル位置、`disptopbp`/`disptopix` が画面左上位置を指します。ノードが満杯になると自動的に `newTBuf()` で次ノードへ分割し、ガベージコレクション (`gabagecollect1/2`) がノード間の断片を詰め直します。

Sources: [editor.c:34-48](), [editor.h:20-25]()

### カーソル移動の設計

カーソル移動関数は常に `cursorbp`/`cursorix`（バッファ位置）と `cx`/`cy`（画面座標）と `disptopbp`/`disptopix`（スクロール位置）を同時に更新します。`cx2` は上下移動時の「意図した X 座標」を保持し、より短い行を通過しても元の列に戻る動作を実現します。

| 関数 | 主要動作 |
|---|---|
| `cursor_left/right` | 1 文字移動・行折り返し・スクロール |
| `cursor_up/down` | 1 行移動（`WIDTH_X` 分遡って改行を探索） |
| `cursor_home/end` | 行頭/行末移動 |
| `cursor_pageup/down` | 画面行数-1 ページ単位スクロール |
| `cursor_top` | バッファ先頭へジャンプ |

Sources: [editor.c:517-840]()

### アンドゥバッファ

`UNDOBUFSIZE=2048` バイトの環状バッファが `undobuf[]` として確保されます。操作ごとに `pushundomem()` でコマンドコードと位置情報を先頭から書き込み、アンドゥ時に `popundomem()` で逆順に読み出して逆操作を適用します。バッファが満杯になった場合は末尾の最古エントリを無効化します。

| コマンド定数 | 内容 |
|---|---|
| `UNDO_INSERT` (1) | 1 文字挿入（アンドゥ時: 削除） |
| `UNDO_OVERWRITE` (2) | 1 文字上書き |
| `UNDO_DELETE` (3) | 1 文字 Delete |
| `UNDO_BACKSPACE` (4) | 1 文字 BackSpace |
| `UNDO_CONTINS` (5) | 連続挿入（貼り付け） |
| `UNDO_CONTDEL` (6) | 範囲削除 |

Sources: [editor.c:213-299](), [editor.h:47-52]()

### シンタックスハイライト

`redraw()` は TVRAM を直接書き換えてシンタックスハイライトを適用します。画面外の行頭まで遡ってパース状態（`inRemark`/`inQuotation`）を復元してから、画面全体を 1 パスでスキャンします。

| 対象 | 色定数 |
|---|---|
| 通常テキスト | `COLOR_NORMALTEXT` (7) |
| BASIC 予約語 | `COLOR_RESERVEDWORD` (16) |
| `REM` コメント行 | `COLOR_REMARKTEXT` (17) |
| ダブルクォート内 | `COLOR_QUOATTEXT` (18) |
| 範囲選択テキスト | `COLOR_AREASELECTTEXT` (4) |

Sources: [editor.c:427-510](), [editor.h:28-36]()

### ファイル操作（SD カード）

`savetextfile()` はリンクリストを順にたどりながら `FILEBUFSIZE=256` バイト単位でバッファリングして FatFS 書き込みを行い、`\n` を `\r\n` に変換します。`loadtextfile()` は逆に `\r` を無視して `\n` のみ保持します。ファイルエラー時は `filesystemretry()` が再試行か ESC 中断を促します。拡張子 `.HEX` のファイルは `runHex()` に直接転送されます。

Sources: [editor.c:1462-1532]()

### キーボード版ファイルブラウザ（`editor.c` 内）

エディタのロード/セーブ操作に呼び出される `select_dir_file()` は、USB キーボードの `inputchar()` / `vkey` を使ってファイルを選択します。`interface/fileselect.c` のゲームパッド版と同じ `file_sort()` 関数（editor.c で定義）を共有しますが、操作系は完全にキーボード対応です。

| キー操作 | 動作 |
|---|---|
| ↑↓←→ / Num8246 | カーソル移動・スクロール |
| Enter / テンキー Enter | 選択確定・新規ファイル名入力 |
| ESC | キャンセル |
| F1 | タイムスタンプ表示切り替え |
| F3 / Shift+F3 | ソート順変更 |
| Ctrl+↑ | 親ディレクトリへ移動 |

Sources: [editor.c:1630-1945]()

### `texteditor()` メインループ

```mermaid
stateDiagram-v2
    [*] --> Init: texteditor() 呼出
    Init --> LoadTemp: WORKDIRFILE/TEMPFILENAME 存在?
    LoadTemp --> MainLoop: 復元完了 or スキップ
    Init --> MainLoop: 一時ファイルなし

    MainLoop --> Redraw: redraw()
    Redraw --> WaitKey: カーソル点滅 + GC
    WaitKey --> NormalChar: ASCII 文字
    WaitKey --> ControlChar: 仮想キー(VK_*)
    NormalChar --> MainLoop: normal_code_process()
    ControlChar --> MainLoop: control_code_process()
```

Sources: [editor.c:2757-2825]()

---

## まとめ

Phyllosoma の入力ハンドリングは、コンパイル時に選択される 2 つのモードを同一の BASIC ランタイム API で抽象化しています。`withkeyboard.c` ビルドは TinyUSB ホスト → `usbkb_task()` → `keycodebuf` リングバッファ → `usbkb_readkey()` → `keyinput.c` 高水準 API という多段パイプラインを Core1 ポーリングで動かし、`editor.c` の双方向リンクリストテキストバッファと 2048 バイトアンドゥ履歴を持つフルフィーチャーエディタへ入力を供給します。`withoutkeyboard.c` ビルドはすべてのキーボード関連構造をスタブ化し、`getchar_timeout_us()` ベースの USB CDC シリアル I/O で同じ API を実装します。SD カードファイルブラウザは用途に応じて GPIO ボタン版（`interface/fileselect.c`）とキーボード版（`editor.c` 内 `select_dir_file()`）の 2 実装が存在し、ビルドターゲットに応じて使い分けられます。

Sources: [CMakeLists.txt:108-160](), [withkeyboard.c:27-102](), [withoutkeyboard.c:25-90]()
