diff --git a/meshterm/ui/menu.py b/meshterm/ui/menu.py index b1f335c..3496405 100644 --- a/meshterm/ui/menu.py +++ b/meshterm/ui/menu.py @@ -186,8 +186,29 @@ async def _menu_loop(ctx: AppContext, session: TuiSession) -> None: selection = await session.select( "What would you like to do?", items, default=last_selection ) + # Both picking "quit" and pressing Esc at the top level ask to leave; confirm on a + # small dialog first so a stray key doesn't drop the user out of the session. Enter + # takes the highlighted default (Quit) and leaves; Esc — or choosing "Stay" — returns + # to the menu (select maps a cancel to None). if selection in (None, "__quit__"): - return + leave = await session.select( + "Leave MeshTerm?", + [ + Choice(title="Quit and disconnect", value="quit"), + Choice(title="Stay", value="stay"), + ], + default="quit", + wrap=False, + filterable=False, + footer_hint="↑↓ move · Enter quit · Esc stay", + ) + if leave == "quit": + return + # Keep the cursor on "quit" when that is what they chose, so a follow-up + # attempt lands where they expect; a stray Esc leaves it where it was. + if selection == "__quit__": + last_selection = "__quit__" + continue last_selection = selection await _run_selection(ctx, selection) diff --git a/meshterm/ui/tui/session.py b/meshterm/ui/tui/session.py index acfd089..7e5494e 100644 --- a/meshterm/ui/tui/session.py +++ b/meshterm/ui/tui/session.py @@ -146,12 +146,26 @@ class TuiSession: self.pop(screen) async def select( - self, title: str, items: list, *, default: Any = None, wrap: bool = True + self, + title: str, + items: list, + *, + default: Any = None, + wrap: bool = True, + filterable: bool = True, + footer_hint: Optional[str] = None, ) -> Any: - """Show a select screen; return the chosen value or ``None`` if cancelled.""" - result = await self.run_screen( - SelectScreen(title, items, default=default, wrap=wrap) + """Show a select screen; return the chosen value or ``None`` if cancelled. + + ``filterable`` and ``footer_hint`` are forwarded to the screen for short, fixed + lists (a yes-or-no style choice) that want no type-to-filter and a tailored hint. + """ + screen = ( + SelectScreen(title, items, default=default, wrap=wrap, filterable=filterable, footer_hint=footer_hint) + if footer_hint is not None + else SelectScreen(title, items, default=default, wrap=wrap, filterable=filterable) ) + result = await self.run_screen(screen) return None if result is CANCEL else result async def select_startup(