Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions lib/devbase/commands/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions tests/cli/test_project_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -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) を押すとトップメニューへ戻る。"""
Expand Down
Loading