From 9df788b5c55fa3445cd86a7768d7a7e6e34ba871 Mon Sep 17 00:00:00 2001 From: blasty Date: Sat, 25 Jul 2026 12:38:17 +0200 Subject: ui: drop the footer cheatsheet; F1 opens a key reference instead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The permanent Footer spent a screen row on a truncated, always-visible key list. Remove it (the status line now owns the bottom row) and put the full cheatsheet behind F1 — grouped by task (Navigate / Views / Move / Edit / Search) rather than by widget, which is what makes it readable. Esc, F1 or q closes it; there's also a "Keyboard shortcuts" command-palette entry. F1 rather than '?' because '?' is already search-backwards in the code views. Textual leaves F1 unbound (App only claims ctrl+q/ctrl+c), so it traps cleanly. Gotcha worth recording: the helper that builds the cheatsheet was first called _render, which collides with Widget._render — Textual invoked ours internally and got a rich Text where it wanted a Visual, so the whole screen failed to paint ("'Text' object has no attribute 'render_strips'"). Renamed to _cheatsheet. The search scenario's "rendered above the footer" check now asserts the search input owns the bottom row instead. New `help` scenario: footer gone, F1 opens it, groups + real bindings present, Esc closes (5 checks). Suite note: view_toggle fails in a full run ONLY as collateral from the standing `filter` flake, which runs immediately before it — when filter leaves the table empty, view_toggle's open_biggest has nothing to open. Verified: view_toggle passes alone (13/13) and directly after `help` (18/18), and reproduces as a cascade with --only filter,view_toggle. Not a regression from this change. --- tests/test_scenarios.py | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) (limited to 'tests') diff --git a/tests/test_scenarios.py b/tests/test_scenarios.py index 1a55f23..c294a9f 100644 --- a/tests/test_scenarios.py +++ b/tests/test_scenarios.py @@ -25,11 +25,11 @@ import traceback sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from idatui.app import ( # noqa: E402 ConfirmScreen, DecompView, DisasmView, FunctionsPanel, HexView, IdaTui, - ListingView, StringsPalette, StructEditor, SymbolPalette, XrefsScreen, - _str_display, _word_occurrences, + HelpScreen, ListingView, StringsPalette, StructEditor, SymbolPalette, + XrefsScreen, _str_display, _word_occurrences, ) from textual.widgets import ( # noqa: E402 - DataTable, Footer, Input, OptionList, Static, TextArea, + DataTable, Input, OptionList, Static, TextArea, ) from rich.text import Text # noqa: E402 from idatui._sync import wait_for # noqa: E402 @@ -340,6 +340,30 @@ async def s_command_palette(c: Ctx): await c.wait(lambda: app._active != "hex", 5) +@scenario("help") +async def s_help(c: Ctx): + app = c.app + st = app.query_one("#status", Static) + c.check("the status line owns the bottom row (no footer cheatsheet)", + st.region.y + st.region.height == app.size.height, + f"status={st.region} screen={app.size}") + await c.press("f1") + opened = await c.wait(lambda: isinstance(app.screen, HelpScreen), 10) + c.check("F1 opens the key cheatsheet", opened, + f"screen={type(app.screen).__name__}") + if not opened: + return + txt = str(app.screen.query_one("#help-text", Static).render()) + c.check("it lists the key groups", + all(s in txt for s in ("Navigate", "Views", "Move", "Edit", "Search")), + txt[:70]) + c.check("it documents real bindings", + "set type" in txt and "split view" in txt and "cross-references" in txt) + await c.press("escape") + await c.wait(lambda: not isinstance(app.screen, HelpScreen), 10) + c.check("Esc closes it", not isinstance(app.screen, HelpScreen)) + + @scenario("strings") async def s_strings(c: Ctx): app = c.app @@ -933,10 +957,10 @@ async def s_search(c: Ctx): await c.pause(0.1) c.check("search bar visible, status hidden (no overlap)", si.display and not status.display, f"si={si.display} status={status.display}") - footer = app.query_one(Footer) - c.check("search input is rendered above the footer (not overlapping)", - si.region.height >= 1 and si.region.y < footer.region.y, - f"search={si.region} footer={footer.region}") + c.check("search input owns the bottom row (nothing overlaps it)", + si.region.height >= 1 + and si.region.y + si.region.height == app.size.height, + f"search={si.region} screen={app.size}") for ch in term: await c.press(ch) await c.pause(0.05) -- cgit v1.3.1-sl0p