| Commit message (Collapse) | Author | Age | Files | Lines |
| ... | |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Instead of parsing pseudocode text, add structured server tools (server/
patch_server.py, alongside del_type):
- func_types(addr): prototype + local variables (name/type/is_arg)
- set_lvar_type(addr,var,type): retype a decompiler local, working on auto/
register vars too (stock set_type only updates already-user-modified lvars)
'y' in a code view retypes what's under the cursor: a local variable (prompt
prefilled with its current type) or a function (prompt prefilled with its full
prototype). Applies via set_lvar_type / set_type, then recompiles + refreshes.
Copy-line moved off 'y' to Ctrl+Y.
domain: Program.func_types / set_function_type / set_lvar_type (LVar/FuncTypes).
Pilot: function-prototype retype (prefill + apply). full suite 94/94.
|
| |
|
|
|
|
|
|
| |
After a successful declare, re-fetch the canonical struct_source and put it back
in the editor, so the normalized layout/types show immediately instead of only
after re-selecting the struct from the list (e.g. a pasted one-liner becomes
multi-line, 'unsigned long' -> 'unsigned __int64'). _loaded_src is updated to the
formatted text so it isn't considered dirty. Pilot check. full suite 92/92.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A struct with a field IDA's C parser rejects (notably '__unused', a predefined
macro) failed to re-declare, but the failure was silent: the edit looked applied,
then reloading showed the old definition (lost work). Now:
- a failed declare shows a loud, styled 'save failed' status and, when it can,
names the offending reserved field ('__unused') instead of IDA's empty
'Failed to parse'; the type is left unchanged and the editor keeps your text.
- switching structs / starting new / closing with unsaved edits prompts to
discard first (ConfirmScreen), so edits aren't silently reloaded away.
Pilot: rejected save is loud/named/non-destructive; switching structs guards
unsaved edits. full suite 91/91.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
The app captures the mouse (SGR tracking), so terminal/tmux drag-select can't
grab text. Add explicit copy actions:
- code views: 'y' copies the current line
- struct editor: Ctrl+Y copies the selection, else the whole C definition
App._copy() emits OSC 52 (Textual copy_to_clipboard) AND, inside tmux, pipes
through 'tmux load-buffer -w -' — the combination is what actually reaches the
system clipboard over tmux/ssh. Status shows the char count.
full suite 87/87.
|
| |
|
|
|
|
|
|
|
|
| |
Delete was wired to the Del key but undiscoverable (modal has no footer) and
unguarded. Add a 'd' alias (Del still works), and route delete through a small
ConfirmScreen (Enter/y confirm, Esc/n cancel) since it's destructive. 'd' in the
editor pane types normally; only the list triggers delete. Hint updated to
'd/Del delete'.
Pilot: 'd' opens the confirm, Esc cancels (kept), Enter deletes. full suite 85/85.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
New StructEditor overlay: left is the list of local structs/unions, right is an
editable C definition. Enter loads a struct (reconstructed as C from its member
layout via type_inspect), Ctrl+S declares it (create or update via declare_type),
Ctrl+N starts a new one, Del deletes, Esc returns to the list then closes.
Domain: Struct model + Program.list_structs / struct_source / declare_type /
delete_type (anonymous $-types filtered).
Delete needs a 'del_type' tool the ida-pro-mcp server doesn't currently expose;
delete_type detects its absence and reports a clear message instead of failing.
Create/read/update work fully.
Pilot checks: open, list, view C, create, update-in-place, delete(-or-report),
close. full suite 84/84.
|
| |
|
|
|
|
|
|
|
|
|
| |
Opening a function that Hex-Rays can't decompile (import/PLT stubs, mis-analyzed
monsters) now shows the disassembly instead of a '/* decompilation failed */'
panel. A separate _pref (the preferred view, set by Tab) is kept, so only the
failing function falls back — the next decompilable function still opens as
pseudocode. _apply_decomp ignores a stale result if you've navigated away.
Pilot checks: failing func -> disasm (pref kept), next func -> pseudocode.
full suite 77/77.
|
| |
|
|
|
|
|
|
|
|
| |
Opening a function now shows the decompiler by default (Tab still toggles to
disassembly; the choice persists across opens). Startup focuses the decompiler
view, and Ctrl+B / back focus the active view rather than hard-coding disasm.
Tests: assert pseudocode-by-default on the first open, then normalize per-section
(the disasm-scrolling/search/follow blocks switch to disassembly explicitly).
full suite 75/75.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Replace the docked names pane as the primary way to jump to symbols with a
command-palette overlay: Ctrl+N opens SymbolPalette, type to fuzzy-find (scored
subsequence match with matched-char highlighting), up/down to select, Enter to
open, Esc to close. Mouse-clickable too.
The docked FunctionsPanel now starts hidden and is still reachable/toggleable
with Ctrl+B (classic table view, sort, filter, goto all unchanged); a code view
takes focus on startup. Status hints 'Ctrl+N: find symbol'.
Pilot checks for open/fuzzy/subsequence/select/close. Tests reveal the docked
pane (Ctrl+B) for the existing table-driven checks. full suite 74/74.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Opening the xref dialog with 'x' now highlights the entry for the current site
(the instruction/pseudocode line under the cursor), so a long xref list can be
stepped through systematically without losing your place.
Capture the cursor's ea span when 'x' is pressed (disasm: the instruction .. the
next; decomp: the line's /*0xEA*/ anchor .. the next), and pre-select the xref
whose frm matches exactly, else the one within that span. Threaded through
_xrefs_{disasm,decomp} -> _xrefs_present -> XrefsScreen(preselect), which sets
OptionList.highlighted (also scrolls it into view).
Pilot check for the decomp path. full suite 68/68.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The xref list showed just the containing function name, so multiple sites in the
same function were indistinguishable ('sub_2300' repeated), and references not in
any function collapsed to a bare '?'.
- In-function sites now show fn_name+offset (e.g. main+0xb08), so each site is
distinct and you can see where in the function it is.
- Non-function sites (GOT/reloc data slots, loose thunks) show the section they
live in (.got, .data.rel.ro, .text, LOAD, ...) instead of '?'. Add
Program.sections()/section_of() over survey_binary's segment map (fetched once,
cached lazily).
Pilot checks for both. full suite 67/67.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Previously an xref jump only positioned the pseudocode line; the cursor sat at
column 0. Thread the referenced symbol's name (the token xrefs was invoked on,
or the callee's name) from the xref dialog through _on_xref_chosen -> _do_navigate,
and place the column at that token's whole-word start on the target line
(_decomp_col_for, computed on the marker-stripped line so it matches the display).
Works for both cross-function (via _apply_decomp cursor_x) and same-function (via
DecompView.goto) jumps.
Add a column assertion to the xref pilot test (asserts token-start when the token
is on the line, line-start otherwise -- robust to a stale server Hex-Rays cache).
full suite 65/65.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
DecompView.goto derived its scroll with _scroll_cursor_into_view() -> a plain
scroll_to, which updates scroll_offset but doesn't repaint at the new offset
(Textual only repaints on a rounded scroll change, and there's no layout pass
since the pane isn't reloaded). So a same-function xref jump landed the cursor
correctly but painted the old scroll until the next cursor move nudged a repaint.
Route goto's scroll (derived or explicit) through _apply_scroll, which re-applies
after the next refresh with layout=True -- the same pattern show() uses. The
cross-function path already worked because reloading + clearing the loading cover
forces a fresh paint. Document the gotcha in TEXTUAL_NOTES.
full suite 64/64.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The earlier xref->pseudocode-line fix only worked when the jump target was in a
DIFFERENT function (which reloads the pane). When the target was inside the
function already displayed, _show_active skipped the reload -> and never applied
the new dec_cursor, so the cursor stayed put (the reported 'jumping to xref in
decompile view doesn't work'; most xrefs land in the shown function).
_open_entry now detects the already-loaded case and repositions the decompiler
via a new DecompView.goto (moves cursor/scroll on the loaded text, no
re-highlight). Toggle (Tab) still goes through _show_active untouched. Add a
same-function xref-jump pilot check.
full suite 64/64.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Hex-Rays appends a /*0xEA*/ VMA marker to every pseudocode line (we fetch with
include_addresses). It's the only per-line address anchor the app has, so we
can't turn it off server-side without breaking follow/comments/xref-jumps.
Instead, strip it at display time: DecompView.show pulls each line's marker into
a parallel _line_eas list and highlights the cleaned text. _line_ea now reads
that list; _follow_decomp takes the line ea explicitly (was re-parsing the
marker). Stripping only edits within lines, so indices still align with the
domain's raw code (used by _decomp_line_for). Var-location comments (// [rsp+..])
are Hex-Rays output, not VMAs -> kept.
full suite 63/63.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Bind ';' (NavMixin, so both code views) to a comment prompt for the line under
the cursor. Resolve the line's address (_cursor_ea in disasm, the /*0xEA*/ marker
in pseudocode), prefill any existing comment (parsed from the rendered '//' text),
and apply via the set_comments tool; an empty value clears it.
After setting, reuse the name-generation invalidation (bump_names) so Hex-Rays is
force_recompiled lazily and the pseudocode/disasm refresh in place at the saved
position -- the new '// comment' shows on the line. Marks the IDB dirty (Ctrl+S).
Pilot checks for the pseudocode round-trip. full suite 63/63.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Pressing 'n' on a Hex-Rays goto label (LABEL_n, or any 'goto TARGET') in the
decompiler routed to a 'local' rename, which the API rejects with a misleading
"Local variable 'LABEL_114' not found" — the user just saw a cryptic failure.
The rename tool has no label category (only func/global/local/stack) and idalib
exposes no other way to rename pseudocode labels.
Detect a label token under the cursor (_is_pseudocode_label) and report the
limitation up front instead of opening a prompt that's doomed to fail. Add a
pilot check (uses main's labels when the current function has none).
full suite 61/61.
|
| |
|
|
|
|
|
|
|
|
|
| |
- test_tui.py: add --stop-after <substr>, ending the run once a matching check
has been evaluated so you can iterate on one feature without paying for the
expensive tail (e.g. 'search finds' -> 5.6s vs 34.6s full). Default unchanged.
- app: skip _decomp_line_for on a plain function-entry jump (line 0 in both
views), avoiding a needless decompile on every by-name navigation in the
decompiler pane.
full suite 60/60.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Selecting an xref (or any address navigation) only positioned the disasm cursor
(NavEntry.cursor via index_of_ea); the pseudocode pane's dec_cursor stayed 0, so
in the decompiler view the jump landed at the top of the target function instead
of on the referencing line.
When the decompiler pane is active, resolve the target ea to its pseudocode line
via the per-line /*0xEA*/ markers (_decomp_line_for) and seed NavEntry.dec_cursor
so _apply_decomp lands + scrolls there. Add a pilot check for it.
Also halve the literal pilot.pause() settle delays in the suite (wait_until poll
step untouched): 40s -> 34.6s wall, stable across 3 runs.
|
| |
|
|
|
|
|
|
|
|
|
| |
xrefs_from on a call/branch returns the ordinary fall-through edge (target ==
the next instruction) as an indistinguishable type=code xref, and _follow_disasm
picked the first code edge -> 'follow' just advanced one line instead of entering
the callee (e.g. Enter on 'call cs:memset_ptr' stayed in the same function).
Plumb the next instruction's ea from the view and drop the edge whose target is
that next instruction, so follow lands on the real call/jump target. Mirrors the
ordinary-flow fix already applied to the xrefs path (e6375de).
|
| |
|
|
|
|
|
|
|
|
|
| |
xrefs data doesn't distinguish a call/jump from ordinary flow (both type=code),
so _xrefs_disasm's 'first code from-xref' subject picked the fall-through to the
NEXT instruction on a plain line. Its only xref is the flow edge back, so
selecting it jumped to the current function at line 0 instead of a real caller.
Drop the from-xref subject entirely: use the symbol under the cursor if any, else
the current address (xrefs-to a function entry == its callers). Now selecting an
xref lands on the exact referencing function AND line. pilot suite 59/59.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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.)
|
| |
|
|
|
|
|
|
| |
force_recompile takes 'items=[{addr}]', not 'addr' — my call raised and was
swallowed, so the Hex-Rays cache was never cleared and a caller's cached
pseudocode kept showing the old callee name on 'back' (disasm refreshed because
its names are live). Use the correct params. History test now also caches and
verifies the caller's pseudocode. pilot suite 52/52.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
- 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
|