From b3c7ea2a3a13ee4c3d07ec1c046252566b4483cf Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Tue, 9 Jun 2026 13:42:46 +0000 Subject: [PATCH] =?UTF-8?q?feat(list):=20list=20TUI=20=E3=82=B5=E3=83=96?= =?UTF-8?q?=E3=83=A1=E3=83=8B=E3=83=A5=E3=83=BC=E3=81=AE=E3=80=8C=E6=88=BB?= =?UTF-8?q?=E3=82=8B=E3=80=8D=E3=81=AB=20=E2=86=90=20=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=81=97=E5=8D=B3=E5=BF=9C=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Esc は矢印キーのエスケープシーケンス先頭バイトと衝突するため、 prompt_toolkit のフラッシュ待ち (約0.5秒) 分の遅延が体感される。 左矢印 (\x1b[D) は完結した曖昧さの無いシーケンスなので、これを running 操作サブメニューの主たる「戻る」キーとして即時反応させる。 Esc は互換のため残す。プロンプト文言も「← ・Esc 戻る」に更新。 Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/devbase/commands/project.py | 20 +++++++++++++++----- tests/cli/test_project_list.py | 7 +++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/devbase/commands/project.py b/lib/devbase/commands/project.py index b6fd366..48788bf 100644 --- a/lib/devbase/commands/project.py +++ b/lib/devbase/commands/project.py @@ -224,15 +224,25 @@ def _cancel(event): def _with_escape_back(question): - """Esc 単独押下で ``_MENU_BACK`` を返す select を返す。 + """← / Esc 押下で ``_MENU_BACK`` を返す select を返す。 - Ctrl-C は questionary 既定どおり中止 (``ask()`` が ``None``) のまま残し、Esc - だけを「1 つ前のメニューへ戻る」シグナルに割り当てる。サブメニュー用。 + Ctrl-C は questionary 既定どおり中止 (``ask()`` が ``None``) のまま残し、← と + Esc を「1 つ前のメニューへ戻る」シグナルに割り当てる。サブメニュー用。 + + Esc (``\\x1b``) は矢印キーのエスケープシーケンスの先頭バイトと衝突するため + prompt_toolkit のフラッシュ待ち分の遅延が体感される。左矢印 (``\\x1b[D``) は + 完結した曖昧さの無いシーケンスなので、これを主たる「戻る」キーとして即時に + 反応させ、Esc は互換のため残す。サブメニューは検索絞り込み (use_search_filter) + を使わないため、← をカーソル移動と衝突させずに割り当てられる。 """ + from prompt_toolkit.keys import Keys + def _back(event): event.app.exit(result=_MENU_BACK) - return _add_escape_binding(question, _back) + _add_escape_binding(question, _back) # Esc(互換・低速) + question.application.key_bindings.add(Keys.Left)(_back) # ←(即時) + return question def _show_menu(rows: list[dict]) -> int | None: @@ -271,7 +281,7 @@ def _show_action_menu(name: str): ] question = questionary.select( f"'{name}' は起動中です。操作を選択 " - "(↑↓ 移動 / Enter 決定 / Esc 戻る / Ctrl-C 中止):", + "(↑↓ 移動 / Enter 決定 / ← ・Esc 戻る / Ctrl-C 中止):", choices=choices, use_arrow_keys=True, use_shortcuts=False, diff --git a/tests/cli/test_project_list.py b/tests/cli/test_project_list.py index e6180af..88207dc 100644 --- a/tests/cli/test_project_list.py +++ b/tests/cli/test_project_list.py @@ -809,6 +809,13 @@ def test_with_escape_back_returns_sentinel_on_escape(): esc[0].handler(types.SimpleNamespace(app=fake_app)) assert captured == {"result": project_mod._MENU_BACK} + # ← (Left) も「戻る」に割り当て、Esc のフラッシュ待ち遅延を回避して即応させる + left = [b for b in q.application.key_bindings.bindings if Keys.Left in b.keys] + assert len(left) == 1 + captured.clear() + left[0].handler(types.SimpleNamespace(app=fake_app)) + assert captured == {"result": project_mod._MENU_BACK} + def test_tui_running_action_escape_returns_to_top_menu(monkeypatch): """running 行のサブメニューで Esc (_MENU_BACK) を押すとトップメニューへ戻る。"""