| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
| |
Right after a rename there's a window where the pseudocode cursor word is still
the OLD name while the symbol table already has the new one, so name-based decomp
follow (refs / resolve / _ref_on_line) all miss -> 'nothing to follow'. Disasm
never hit this because it falls back to an address xref.
Add the same address-based fallback to _follow_decomp: parse the line's /*0xEA*/
marker and follow a code xref from that statement — immune to a stale name.
Verified: with the old token 'sub_53D20' (renamed away) it still follows to the
right address. pilot suite 58/58.
|
| |
|
|
|
|
|
| |
Was 42% (≈100 cols on a 240-wide term). Now 30% clamped to min 42 / max 44, and
columns tightened to 10/21/7 -> a stable ~41-43 cols that fits the content: 46%
on a 90-col term but only 18% at 240 (code area 194 vs ~140 before). Ctrl+B still
hides it. pilot suite 57/57.
|
| |
|
|
|
|
|
| |
Fixed left gutter with right-aligned line numbers (dim; brighter on the cursor
line); the code scrolls horizontally past the gutter while the numbers stay put.
Column cursor, search-column, and mouse mapping account for the gutter offset
(_col_offset); virtual width includes it. pilot suite 56/56.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
When a decompile runs (tab to pseudocode, or an implicit refresh after rename),
DecompView.loading covers the pane with a centered, grayed '― decompiling… ―'
(custom get_loading_widget + .decomp-loading CSS), cleared when the body arrives.
The loading cover blurs the widget, which made Tab fall back to focus-nav instead
of toggling; made the app Tab/Shift+Tab binding priority so it toggles regardless
of focus, and restore focus to the pane when the decompile finishes.
pilot suite 55/55 (adds overlay-shown / overlay-cleared).
|
| |
|
|
|
|
|
|
|
|
|
| |
Textual's ScrollView.watch_scroll_y only repaints when the rounded scroll
changes, and an immediate scroll_to right after setting virtual_size clamps to 0
when the view's size isn't computed yet (e.g. just made visible). DecompView.show
had no deferred fallback at all, so it could stick at the top until a cursor move.
Add a shared ColumnCursor._apply_scroll: set the offset now AND re-apply it after
the next refresh with refresh(layout=True), forcing a repaint at the restored
offset. Used by DisasmView._on_primed and DecompView.show. pilot suite 53/53.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
The pane could keep showing the top (scroll 0) after jumping back even though
scroll_offset was correct: the deferred call_after_refresh set the scroll AFTER
the paint and didn't force a repaint. Now the deferred callback re-applies the
scroll AND refreshes, so the pane is repainted at the restored offset.
Crucially, the prior test only checked scroll_offset.y (which was correct even
when the bug was visible). Added a render-level assertion: trace render_line and
assert the last repaint happened at the restored scroll. (Also: my earlier
scratch repros used a non-existent function name so 'back' was a no-op -- the
real suite test uses a valid second function.) pilot suite 53/53.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
The restored scroll was correct in the final state but load() zeroed
virtual_size (snapping scroll to 0) and only a deferred call_after_refresh
jumped to the target -> a visible flash to the top before settling. Now: don't
zero virtual_size on load, and apply the target scroll synchronously in
_on_primed (plus the deferred call as a fallback for un-sized regions).
Also hardened the scroll test to use a MID-viewport cursor (rel>0). The prior
test had the cursor at the viewport top (rel=0), where scroll-into-view derives
the same scroll -> it would have passed even if scroll restore were broken.
pilot suite 52/52.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Root cause of 'Maximum idalib worker count reached': the app bumped idle_ttl to
~never on every open, so sessions never freed and hit the default cap of 4.
- app: drop the immortal bump_idle_ttl(1e9); rely on the KeepAlive heartbeat
(120s) to keep the running session warm, and open with a moderate idle_ttl
(1800s) so the worker self-exits and frees its slot after the TUI closes.
- spawn.sh: set IDA_MCP_MAX_WORKERS (default 8) for headroom.
- doc: supervisor prunes unreachable workers before counting, so killing an idle
worker frees a slot on the next open.
(Verified separately: big/truncated decompile results propagate renames fine via
the earlier force_recompile fix.)
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
- Visibility: #search/#rename/#status were dock:bottom like the Footer and got
placed on the SAME row (y=39) as the Footer, which drew over them, so the
typed text was invisible. Drop the docking; they now sit in normal flow just
above the Footer (y=38). Verified: input region is above the footer.
- Stale names on 'back': a rename only invalidated the current function, so
popping back to a caller (cached earlier) showed the old name. Add
Program.bump_names(): a rename bumps a name-generation, clears all disasm block
caches (disasm names are live), and decompilation is generation-checked and
force-recompiled lazily on next view. _after_rename now invalidates globally.
- verified: rename a callee, 'back' to the caller shows the new name.
- pilot suite 51/51.
|
| |
|
|
|
|
|
| |
PageUp/Down and Ctrl+U/D now move the scroll top and the cursor by the same
(clamped) delta, so the cursor keeps its relative screen row instead of snapping
to an edge; at the very top/bottom the cursor jumps to the extreme. Shared
_page_scroll in ColumnCursor, used by both views. pilot suite 49/49.
|
| |
|
|
|
|
|
| |
_goto_line now sets cursor_x to the first match range's start on the target line
(and _hscroll's it into view for the pseudocode view), so jumping to a search hit
positions the cursor exactly on the match, not the old column. Search cancel/empty
also restore the origin column. pilot suite 47/47.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Restoring only the cursor re-derived the viewport via scroll-into-view, so back
landed on the right line but scrolled differently. NavEntry now stores scroll_y
(disasm) and dec_scroll_y/dec_scroll_x (pseudocode); a single _save_current_pos()
snapshots cursor+scroll of both views right before navigating away, and load/show
restore the exact scroll. Disasm defers scroll_to via call_after_refresh (its
virtual_size is only set in _on_primed, so an immediate scroll_to clamps to 0).
Also routes rename's reload through the same save/restore path.
pilot suite 46/46.
|
| |
|
|
|
|
|
|
|
|
| |
_follow_decomp / _xrefs_decomp only matched the pseudocode ref list, so a
function name under the cursor that wasn't in refs (self-reference, or refs
truncated on big functions) did nothing — while disasm worked because it also
resolves the symbol. Both decomp paths now fall back to program.resolve(word)
like disasm does.
pilot suite 45/45.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- 'n' on the token under the cursor opens a rename prompt prefilled with the
current name. The symbol is classified and routed to the right rename batch:
* resolves to a function start -> func
* decompiler ref (global/string) -> data
* pseudocode token, not a ref -> local (Hex-Rays lvar)
* disasm var_/arg_ -> stack
tool errors (e.g. name collisions) are surfaced in the status line.
- after a rename: force_recompile + cache invalidation, reload the current views
(preserving cursor), update the renamed function's row in place (avoids racing
the initial stream), and update nav history names.
- 'Ctrl+S' persists the IDB (idb_save); edits mark the session dirty.
- 'n'/'N' search-repeat dropped for 'n'=rename (repeat stays on '/'+Enter / '?').
- verified live: func rename (list + disasm update), local var rename (pseudocode
update), save. pilot suite 44/44; domain 17/17.
|
| |
|
|
|
|
|
| |
- on_data_table_header_selected sorts the cached function list by the clicked
column (address / name / size); clicking the same header reverses. Header
label shows a ▲/▼ arrow. Sorting composes with the active filter.
- pilot suite 41/41.
|
| |
|
|
|
|
|
|
|
|
| |
NavEntry now also stores dec_cursor/dec_cursor_x. DecompView posts a CursorMoved
(with the line's /*0xEA*/ address) on move/click; the app keeps the top-of-history
pseudocode position current and DecompView.show restores it when returning to a
function. So Esc after a jump lands on the exact pseudocode line+column too,
independent of the disasm position.
pilot suite 38/38.
|
| |
|
|
|
|
|
|
|
| |
NavEntry now stores cursor_x too; the top-of-history position is kept current
(line + column) on every disasm cursor move, and DisasmView.load restores the
column (clamped once the line is cached). So Esc after a jump lands exactly on
the symbol you left from, not the line start.
pilot suite 37/37 (adds 'back restores the exact line + column').
|
| |
|
|
|
|
|
|
|
|
|
| |
- ColumnCursor.on_click maps the click to a virtual (line, column) via
event.get_content_offset (handles padding/border) + scroll_offset, then places
the line+column cursor (disasm posts CursorMoved for the status line).
- double-click (event.chain>=2) = place cursor + follow the symbol under it,
identical to selecting it and pressing Enter.
- works in both views (verified: disasm click on 'call sub_XXXX' lands on the
exact token; pseudocode click maps columns with no padding).
- pilot suite 36/36.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
- ColumnCursor mixin for both views: h/l/left/right, w/b word-hops, 0/$ line
ends; block cursor cell + word-under-cursor highlight rendered on the cursor
line (region-refresh so it stays cheap). vertical moves clamp the column.
- follow/xrefs now use the token under the cursor, so a line with multiple refs
follows the one you're pointing at (proven: 'return sub_A(sub_B,..)' -> cursor
on sub_B follows sub_B). decomp matches a decompiler ref by exact name; disasm
resolves the symbol, falling back to the line's from-xref (address column /
hex operands are excluded so the default position still follows the target).
- horizontal scroll follows the cursor in the pseudocode view.
- dropped the f/b paging aliases (b is now word-back).
- pilot suite 34/34.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- Enter follows the reference under the cursor: disasm uses xref_query(from) to
find the code target; pseudocode matches a decompiler ref name on the line.
Navigates to the containing function at the exact line (address-history push).
- x opens an xrefs popup (XrefsScreen modal): xrefs_to the subject under cursor
(call target if any, else the current item); Enter jumps to a referencing
site, Esc closes.
- domain: Program.function_of (mid-addr -> containing func via lookup_funcs),
xrefs_from/xrefs_to (xref_query), Xref model, DisasmModel ea->index map for
landing on an exact address.
- fix: goto to a mid-function address now lands on the right line (was opening
it as a bogus function start).
- pilot suite 32/32; domain 17/17.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
- Incremental client-side filter over the cached function list (no per-keystroke
server round-trips): narrows + highlights the matched substring in each name
as you type, debounced ~80ms. Substring or glob (*/?), smartcase.
- Clear the filter: Esc in the filter box, OR Esc on the focused list, OR empty
Enter. Status shows 'filter <q>: N/total'.
- streaming pauses while a filter is active; re-applied over the full set once
load completes. Row selection resolves the name from the index (cells can now
be styled Text).
- FunctionIndex.all_loaded(); pilot suite 27/27.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
- Fix overlap: #search, #status and Footer all docked bottom, and Input defaults
to a 3-row bordered widget squeezed into 1 row. Now the search bar is a clean
1-row (border:none) that REPLACES the status line while active (status hidden),
so the query is always visible.
- Incremental search: highlight all matches + preview-jump from the origin on
every keystroke (Input.Changed), not just on submit. Disasm indexes once then
each keystroke is instant. Enter keeps; empty Enter repeats last; Esc cancels
and restores the origin cursor + status line.
- pilot suite 23/23 (adds: bar-visible/no-overlap, incremental-before-Enter,
Esc-cancel).
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
- SearchMixin shared by both views: '/' search forward, '?' backward, empty query
repeats last; n/N next/prev. Smartcase. All matches highlighted (substring
overlay), cursor lands on current match; status shows k/N.
- disasm indexes line text lazily in a worker (handles huge funcs); pseudocode
has text in hand. Substring highlight via crop/join Strip overlay.
- app: bottom search prompt, SearchRequested routing; '/' is context-sensitive
(filters when the function table is focused, searches in code views).
- gotcha fixed: Textual only merges BINDINGS from DOMNode subclasses, so mixin
bindings are ignored -> each view lists SEARCH_BINDINGS explicitly.
- pilot suite 20/20 (adds search finds/hl/repeat-fwd/repeat-back).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Highlighting: Textual has NO C/C++ tree-sitter grammar (and the syntax extra
wasn't installed), so language='cpp' was a silent no-op. Replace TextArea with a
virtualized, Pygments-highlighted ScrollView:
- idatui/highlight.py: CLexer -> Rich styles, per-line Segment lists
- DecompView: lines highlighted ONCE at load, cached as Strips; O(1) scroll
Jank: profiling showed our code is ~3.6ms/move (render_line 0.018ms) -- the cost
was terminal repaint volume, since every cursor move refreshed the whole ~43-line
viewport. Now:
- cursor reactive repaint=False (no implicit full refresh)
- region-limited refresh: in-place moves repaint only the 2 changed rows
(measured 43 -> 2 render_line calls/move), full refresh only on scroll
Applied to both DisasmView and DecompView.
pilot suite 15/15 (adds highlighting assertion).
|
| |
|
|
|
|
|
|
|
|
|
| |
- domain.decompile now fetches the FULL body: server caps responses at 50KB and
clips strings to 1KB, but caches the full output and exposes it at
_meta.ida_mcp.download_url -> we GET that so pseudocode is complete
(main: 43257 chars, not a 1KB stub). include_addresses gives per-line /*0xEA*/.
- DecompView (read-only TextArea, cpp highlighting best-effort, line numbers)
- Tab and Shift+Tab toggle the code pane; lazy decompile in a worker, cached;
hard-failures ('decompilation failed') shown gracefully with disasm fallback
- pilot suite 14/14 (adds tab->pseudocode, full-body, shift+tab->disasm)
|
| |
|
|
|
|
|
| |
- app: when open_path is set, idb_open (idle_ttl=1e9, long timeout) and pin the
returned session; clean status on failure (e.g. non-writable dir)
- tui.py: --open flag + usage docs (attach vs open)
- verified: --open targets/cat -> new session, 161 funcs loaded
|
| |
|
|
|
|
| |
- action_toggle_functions: collapse/restore left pane, disasm takes full width;
focus follows (disasm when hidden, table when shown)
- pilot test covers the toggle (11/11)
|
|
|
- idatui/app.py: keyboard-first Textual app
* FunctionsPanel: lazy-streamed function list (DataTable) with glob filter
* DisasmView: line-VIRTUALIZED ScrollView -- paints from domain block cache,
background-fetches misses, prefetches neighbors. Proven: 52,120-insn func,
jump-to-bottom in 0.31s with only ~6 blocks (~1.5k lines) ever materialized.
* address-history nav stack (Enter follow / Esc back); goto by name or 0xADDR
* bump_idle_ttl + KeepAlive on startup so the session never idles out
* all MCP work on Textual worker threads; UI never blocks
- idatui/tui.py: launcher (python -m idatui.tui / console script 'idatui')
- tests/test_tui.py: headless Pilot test, 9/9 (boot, load 10k funcs, open,
scroll, bg-cache, goto-bottom, filter)
- pyproject: textual extra + idatui script entry
|