From 41b42e4e7ebb9303e7a861ee8c6d948d8cd813fa Mon Sep 17 00:00:00 2001 From: blasty Date: Fri, 24 Jul 2026 21:48:20 +0200 Subject: split-view phase 1: side-by-side listing <-> pseudocode layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Ghidra-style dual view, layout + toggle (no cursor sync yet — that's phase 2). 's' (and a "Split view" palette command) toggles a _split mode where the listing (left) and pseudocode (right) show together, divided by a keyline, one focused. Entering split loads the listing + decompiles the current function into both panes; Tab/F5 switches the focused pane; any single-view target (hex, etc.) or 's' again collapses back to the focused pane. * app: _split flag; _show_active gains a split branch (both panes, load decomp, focus active, #panes.split class); action_toggle_split + _enter_split worker; action_toggle_view switches panes when split; 's' binding + palette entry; #panes.split ListingView divider CSS. * docs/SPLIT_VIEW.md: the full design + phased roadmap (the hard part — line<->EA set mapping via a sweep of cfunc.get_line_item — is scoped for phase 3). * tests: split_view scenario (enter/load/tab-focus/exit, 5 checks). Also fix disasm_nav's stale goto-bottom (press ctrl+end; plain 'end' is end-of-line now). Verified live over RPC (both panes render, Tab flips focus, 's' exits) and pilot split_view 5/5. --- tests/test_scenarios.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/test_scenarios.py b/tests/test_scenarios.py index 39f8d86..aa0ecbf 100644 --- a/tests/test_scenarios.py +++ b/tests/test_scenarios.py @@ -338,6 +338,32 @@ async def s_command_palette(c: Ctx): await c.wait(lambda: app._active != "hex", 5) +@scenario("split_view") +async def s_split_view(c: Ctx): + app, lst, dec = c.app, c.lst, c.dec + await c.open_biggest("listing") + await c.press("s") + shown = await c.wait(lambda: app._split and lst.display and dec.display, 20) + c.check("'s' enters split view (both panes shown)", shown, + f"split={app._split} lst={lst.display} dec={dec.display}") + loaded = await c.wait(lambda: dec.loaded_ea == app._cur.ea, 25) + c.check("split loads the pseudocode alongside the listing", loaded, + f"loaded={dec.loaded_ea} cur={app._cur.ea if app._cur else None}") + await c.press("tab") + await c.pause(0.1) + c.check("Tab in split focuses the pseudocode pane", app._active == "decomp", + f"active={app._active}") + await c.press("tab") + await c.pause(0.1) + c.check("Tab again focuses the listing pane", app._active == "listing", + f"active={app._active}") + await c.press("s") + gone = await c.wait(lambda: not app._split and lst.display + and not dec.display, 10) + c.check("'s' exits split back to a single view", gone, + f"split={app._split} lst={lst.display} dec={dec.display}") + + @scenario("decomp_fallback") async def s_fallback(c: Ctx): app = c.app @@ -485,8 +511,9 @@ async def s_disasm_nav(c: Ctx): await c.wait(lambda: app._clipboard == cur_line, 10) c.check("Ctrl+Y copies the current code line to the clipboard", bool(cur_line) and app._clipboard == cur_line, f"clip={app._clipboard!r}") - # goto-bottom must not hang on a huge function. - await c.press("end") + # goto-bottom must not hang on a huge function (ctrl+end; plain 'end' now + # moves the cursor to end-of-line). + await c.press("ctrl+end") await c.pause(0.05) c.check("goto-bottom lands near end", view.cursor >= view.total - 1, f"cursor={view.cursor}/{view.total}") -- cgit v1.3.1-sl0p