| Commit message (Collapse) | Author | Age | Files | Lines |
| ... | |
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
- 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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Root cause: idalib workers run an idle watchdog (worker_lifecycle.py) that
self-exits after idle_ttl_sec (default 600s) of no requests -- deliberate
resource hygiene for a shared/ephemeral supervisor, wrong for an interactive
TUI. CLI-opened startup binary always gets 600s (no flag).
Fix (both verified against a 12s-TTL session):
- IDAClient.bump_idle_ttl(1e9): re-open (idempotent) re-applies TTL, no upper
cap -> effectively immortal. Primary fix, call once at startup.
- IDAClient.keepalive()/KeepAlive: background server_health heartbeat resets the
watchdog; covers adopted sessions too. Safety net.
tests/test_keepalive.py: 4/4 (heartbeat + bump both keep a short-TTL worker
alive past 2x TTL). docs updated with why + fix.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
- Program: model registry + small prefetch pool; cache invalidation hooks
- FunctionIndex: lazy pagination (advance by len, clamp page=500), filter globs,
viewport windows; full 10,092-func enumerate matches survey in ~3.1s
- DisasmModel: block-cached windowed disasm (256/block), forward+back prefetch,
cached instruction totals; deep window revisit 196ms -> 0ms (cache proven)
- Decompilation: truncation-aware, hard-failure surfaced as data (monster funcs)
- resolve(): int/hex/symbol via lookup_funcs (string-array query shape)
- tests/test_domain.py: 17/17 on both ls (~290 funcs) and libcrypto (10k+52k-insn)
- smoke_client no longer assumes 'main' exists (works on libraries)
- doc: idle_ttl worker self-exit finding ("not reachable" needs re-open)
|
| |
|
|
|
|
|
|
|
|
|
| |
- auto-recover stale db ("Session not found" after server restart): drop pin,
re-resolve sole session, retry once. Only when db was auto-injected; never
silently switches an explicitly-pinned db (auto_recover_session gate).
- tests/serverctl.sh: start/stop/kill9/ready control over spawn.sh
- tests/stress_client.py: 6 adversarial scenarios, 24 assertions:
dead-port fail-fast, tight-timeout recovery, worker crash, full restart
(naive auto-recovery + no-autoswitch), 300/16 concurrency, kill-under-churn.
All clean: no hangs, no deadlocks, no non-IDAError leaks.
|
|
|
thread-safe
- IDAClient: single handshake, ~7ms warm calls (vs ~60ms cold CLI)
- keep-alive connection pool over http.client; concurrent-safe (40 threads OK)
- grounded error taxonomy: IDAToolError on isError, soft per-item errors as data
- session-expiry recovery (404 -> re-handshake -> retry), bounded transport retries
- auto db-injection + resolve_db (ignores stale empty-id sessions)
- live smoke test: 13/13 pass
|