diff options
| author | blasty <blasty@local> | 2026-07-25 12:23:18 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-25 12:23:18 +0200 |
| commit | 1f04ec9967c4fb4703657458531cbbe958e66b37 (patch) | |
| tree | 3bcfcb51122d1109fe620b7b9eea87a1f3a194e6 | |
| parent | retype: 'y' now retypes globals too, not just prototypes and locals (diff) | |
| download | ida-tui-1f04ec9967c4fb4703657458531cbbe958e66b37.tar.gz ida-tui-1f04ec9967c4fb4703657458531cbbe958e66b37.tar.xz ida-tui-1f04ec9967c4fb4703657458531cbbe958e66b37.zip | |
decomp: Home/End move along the line instead of scrolling to top/bottom
The pseudocode view still had the old mapping (home -> goto_top, end ->
goto_bottom), so <End> jumped you to the bottom of the function instead of the
end of the line. Bring it in line with the listing view:
home start of line ctrl+home top of the function
shift+home first non-blank ctrl+end/G bottom of the function
end end of line
shift+home skips the C indentation — the pseudocode analogue of the listing's
skip-the-address-gutter.
Verified live and locked in view_toggle (5 checks): <end> stays on the line with
the scroll unchanged, <home> hits column 0, <shift+home> lands on the indent
width, and ctrl+home/ctrl+end still reach top/bottom. Full suite 179/2-flake.
| -rw-r--r-- | idatui/app.py | 18 | ||||
| -rw-r--r-- | tests/test_scenarios.py | 34 |
2 files changed, 50 insertions, 2 deletions
diff --git a/idatui/app.py b/idatui/app.py index 0012e32..f79d04d 100644 --- a/idatui/app.py +++ b/idatui/app.py @@ -1449,8 +1449,13 @@ class DecompView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=True Binding("ctrl+u", "half_page(-1)", "½↑", show=False), Binding("pagedown", "page(1)", "PgDn", show=False), Binding("pageup", "page(-1)", "PgUp", show=False), - Binding("home", "goto_top", "Top", show=False), - Binding("G,end", "goto_bottom", "Bottom", show=False), + # Home/End move the cursor along the line (as in the listing); top and + # bottom of the function move to the ctrl+ pair (G still works too). + Binding("home", "col_home", "bol", show=False), + Binding("shift+home", "col_code_home", "code start", show=False), + Binding("end", "col_end", "eol", show=False), + Binding("ctrl+home", "goto_top", "Top", show=False), + Binding("G,ctrl+end", "goto_bottom", "Bottom", show=False), *SearchMixin.SEARCH_BINDINGS, *NavMixin.NAV_BINDINGS, *ColumnCursor.COL_BINDINGS, @@ -1649,6 +1654,15 @@ class DecompView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=True if round(old_value) != round(new_value): self.post_message(DecompView.Scrolled()) + def action_col_code_home(self) -> None: + """shift+home: first non-blank column — past the C indentation, the + pseudocode analogue of the listing's skip-the-address-gutter.""" + text = self._line_plain(self.cursor) or "" + self.cursor_x = len(text) - len(text.lstrip()) if text.strip() else 0 + self._hscroll() + _refresh_lines(self, self.cursor) + self._refresh_hl() + def line_for_ea(self, ea: int) -> int | None: """The pseudocode line whose marker ea is the largest <= ``ea`` (the C line that best covers an instruction address).""" diff --git a/tests/test_scenarios.py b/tests/test_scenarios.py index b6ed230..1a55f23 100644 --- a/tests/test_scenarios.py +++ b/tests/test_scenarios.py @@ -853,6 +853,40 @@ async def s_view_toggle(c: Ctx): c.check("pseudocode has a numbered gutter (line 1 first)", dec._gutter > 0 and row0[:dec._gutter].strip() == "1", f"gutter={dec._gutter} row0={row0[:10]!r}") + # Home/End move along the line here too (they used to scroll to top/bottom). + line = next((i for i, t in enumerate(dec._texts) + if t.startswith(" ") and t.strip()), None) + if line is not None: + text = dec._texts[line] + dec.focus() + dec.cursor, dec.cursor_x = line, 0 + dec.refresh() + await c.pause(0.05) + top = round(dec.scroll_offset.y) + await c.press("end") + await c.pause(0.05) + c.check("<end> in pseudocode goes to end-of-line, not the bottom", + dec.cursor == line and dec.cursor_x == max(len(text) - 1, 0) + and round(dec.scroll_offset.y) == top, + f"line={dec.cursor} col={dec.cursor_x} len={len(text)}") + await c.press("home") + await c.pause(0.05) + c.check("<home> in pseudocode goes to start-of-line", + dec.cursor == line and dec.cursor_x == 0, + f"line={dec.cursor} col={dec.cursor_x}") + await c.press("shift+home") + await c.pause(0.05) + c.check("<shift+home> skips the indentation", + dec.cursor_x == len(text) - len(text.lstrip()), + f"col={dec.cursor_x} indent={len(text) - len(text.lstrip())}") + await c.press("ctrl+end") + await c.pause(0.1) + c.check("<ctrl+end> still goes to the bottom", + dec.cursor >= dec.total - 1, f"{dec.cursor}/{dec.total}") + await c.press("ctrl+home") + await c.pause(0.1) + c.check("<ctrl+home> still goes to the top", dec.cursor == 0, + f"{dec.cursor}") @scenario("search") |
