aboutsummaryrefslogtreecommitdiffstats
path: root/TODO (unfollow)
Commit message (Collapse)Author
2026-07-26listing: syntax-highlight assembly from IDA's own token tagsblasty
The listing showed the mnemonic bright and every operand in one body colour. IDA already classifies each token, for every processor it supports: generate_disasm_line() emits \x01<tag>text\x02<tag> and the tag says what the text IS. We were calling tag_remove() and throwing that away. So: no lexer. A pygments asm lexer would be a worse guess and would need one dialect per architecture — this is arch-correct for free, including the ARM/MIPS blobs the loader work just made openable. lea rcx, function; "usage" insn reg punct name cmt _idatui_spans() parses the tags into [[kind, text], ...], heads rows carry "spans", Head.spans holds them, and _span_segments() renders them with a fallback to the old mnemonic/rest split for older workers. Palette rule: NEUTRALS for the machine (mnemonic brightest — it's the column you scan; registers at body weight because they're most of the text), HUES only where they mean something (numbers, strings, symbols), structure recedes so commas and brackets stop competing with operands. Two things that fail SILENTLY and are now encoded: * The constants are SCOLOR_DATNAME / SCOLOR_CODNAME. There is no SCOLOR_DNAME — a wrong guess leaves the tag unmapped, symbols render as plain body text, and nothing tells you why. Probed the live IDA to get the real names. * Spans must be whitespace-collapsed exactly as `text` is, walking characters rather than per span, because a run of IDA's column padding straddles span boundaries. A row only gets spans when they reconstruct `text` exactly, so a mismatch degrades to the old rendering instead of corrupting the line. The reason this was parked yesterday was NOT a bug in it. listing_view's "undefining a data head yields an unknown run" waits for `index_of_ea(dea) >= 0` — but dea is the head it just undefined, so it is in the OLD model too and the predicate passes instantly, asserting against pre-edit rows. It only ever passed because the model swap won the race; spans made pages 3x bigger, the swap lost, and the check accused working code. It now waits for the model to be REPLACED. Cost measured on libcrypto: 95KB per 500-row page, 50ms; model ensure(2000) 228ms. Acceptable for what it buys. tests: new asm_highlight scenario (+7) — >90% of code rows carry spans, insn/reg/ punct present, every span kind has a style, spans reconstruct the row text exactly, mnemonic is the first span. 202/0 scenarios, 26/0 blob, 30/0 project UI. TODO: DisasmView appears to be dead code (never instantiated; Ctx.dis returns ListingView), which is why this only needed doing once.
2026-07-26asm highlighting: park the work with its findings (not merged)blasty
IDA already classifies every disassembly token for every processor it supports — generate_disasm_line emits \x01<tag>text\x02<tag> and the tag says what the text IS — so this needs no lexer at all, and certainly not a pygments asm lexer, which would be a worse guess and need one dialect per architecture. Working implementation is in docs/wip-asm-spans.patch and verified on echo: "lea rcx, function; \"usage\"" tokenises as insn/text/reg/punct/text/name/cmt, with sane coverage over 400 rows. Not merged: it breaks listing_view's "undefining a data head yields an unknown run" (reports kind=data). Bisected to this change on a fresh database — 10/0 with it stashed, 9/1 with it applied. The span code doesn't touch `kind`, so the cause is not yet understood, and I would rather park a working-but-unexplained change than merge one that makes a real check lie. TODO records the two non-obvious findings so the next attempt doesn't repeat them: the constants are SCOLOR_DATNAME/SCOLOR_CODNAME (no SCOLOR_DNAME, and a wrong guess fails silently as plain body text), and spans must be whitespace-collapsed by walking characters, not per span, because a run of spaces straddles span boundaries.
2026-07-26app: one way to preserve view state across a model rebuild (ViewAnchor)blasty
Two bugs in two days had the same shape — an edit rebuilds the model and whatever ran last wins. The status message got clobbered by the reload's own status write; the scroll position got recomputed from a row index that no longer meant the same thing. Both were patched by hand. A third was coming. ViewAnchor makes it one thing: where you are looking, in ADDRESSES, plus the message the rebuild must not eat. _anchor() captures it on the UI thread before the edit; _anchor_rows() resolves it against the rebuilt model; _edit_done() settles the aftermath. The edit paths (_do_edit_item, _do_make_data) now hand one object through instead of threading positions and messages separately. Addresses, not indices, because an edit can change how many rows an item takes: four undefined byte rows collapse into one instruction row, undefining does the reverse. An index means a different place afterwards. _reload_active_code deliberately does NOT use it. Renames and comments don't change row structure, and the model that path rebuilds is constructed empty — index_of_ea returns -1 until pages load, so an anchor would resolve to nothing while costing an extra model build on the UI thread. Wrote that out and reverted it rather than leave an abstraction applied where it does nothing. Also fixed in passing: _do_make_data had the same latent bug (no scroll preservation at all) and now goes through the same path. The bigger find is in TODO. The scenario suite mutates targets/echo.i64 and SAVES it, so a scenario that undefines an instruction breaks later runs permanently — decomp_follow_self had been failing on a polluted database, not on any code change. It also made an edit-position check look flaky one run in three, which I nearly wrote up as a race. Coverage for this lives in test_blob_ui.py instead, which builds a throwaway binary and can mutate freely. tests: +1 blob UI (commenting leaves the view where it was), alongside the carve checks. 26/0 blob, 202/0 scenarios (on a fresh .i64), 30/0 project UI.
2026-07-24deprecate: make the idalib worker the default backend; mark mcp path for removalblasty
Opening a binary now defaults to our own idalib worker; the ida-pro-mcp HTTP supervisor path is deprecated (kept only for --db/attach and --backend mcp). * launch.py: --backend default resolves to worker for a fresh binary open, mcp for the attach modes (--db / bare `ida-tui`, which have no worker equivalent); explicit --backend or IDATUI_BACKEND still wins. Logs a deprecation notice when the mcp path is used. * Deprecation markers on client.py and server/patch_server.py; the ida-tui shell header and README now describe the worker as primary and note $IDATUI_WORKER_PYTHON. TODO tracks the removal checklist. No code deleted yet — the mcp fallback stays until the worker is proven on a box where idalib can spawn (pilot against --backend worker is the gate). Backend resolution matrix verified: `ida-tui bash`->worker, bare/`--db`->mcp, explicit flag/env honored.
2026-07-23unify: back the function disasm view on the heads listing walker (M4 item 4)blasty
The function disasm view is now a filtered listing: DisasmModel sources its lines from the heads walker bounded to [func.start, func.end) instead of the disasm tool, so both code views render from the one listing mechanism. DisasmView UI (opcode bytes, o-toggle, Tab, rename) preserved -> no test churn. total() stays on disasm include_total: paging heads for the count hit the MCP response-size limit (count=1000 truncated -> wrong count) and corrupted prime. include_total is one fast exact call; for a code function it equals the heads row count backing the lines. _fetch_block uses count=257 (safe). Verified: disasm-heavy scenarios 56/0; full suite 127/1 (1 = flaky filter). M4 complete.
2026-07-23TODO: M4 progress — string auto-detect + streaming done; ↵blasty
struct-expand/unify deferred
2026-07-22app: 'd' — define typed data in the listing (make_data, M3)blasty
Press 'd' on a listing head to define typed data at that address via a prompt (the ".data type definitions" backlog item). Mirrors the retype prompt flow: MakeDataRequested -> #makedata Input (prefilled with a size-appropriate default type: unsigned __int8/16/32/64 or char[N]) -> _do_make_data worker -> Program.make_data -> bump_items -> reopen the listing in place. Accepts any C type IDA's SetType understands: int, char[16], my_struct, T *arr[4], ... Pilot: listing_view gains a deterministic sub-test — undefine a data head to synthesize an unknown run, assert it renders as `unknown`, then 'd' char[4] over it and assert it becomes a `data` head. 118 pass / 1 pre-existing flaky (filter); rpc_smoke 29/0.
2026-07-22app: ListingView — virtualized flat code+data listing for regions (M2)blasty
The real disassembly-listing view. ListingView is a line-virtualized ScrollView (reusing ColumnCursor/SearchMixin/NavMixin) backed by ListingModel, rendering code, data (db/dw/dd, strings, jump tables) and undefined bytes interleaved with per-kind styling. Non-function regions now open in it instead of the M0 DisasmModel stopgap. Integration (IdaTui): a new `listing` active mode. _open_entry routes region entries to ListingView; _show_active/compose add it; follow/xrefs/comment and the c/p/u edit verbs handle it (define_func upgrades a region straight to the function view); backslash reaches hex and returns via _code_mode (so leaving hex from a region lands back on the listing, not a func view); Tab explains there's no pseudocode for a region. rpc._active_widget learns `listing`. Server fix (the important one): the `heads` walker now steps by get_item_end instead of next_head. next_head SKIPS undefined bytes, so after undefining a function its start address wasn't even a listing line and the cursor snapped to the next defined item; stepping by item-end renders undefined bytes as `db ?` lines (IDA-accurate) and makes navigation land exactly on any address — essential for "go to unmarked bytes and hit c". Needs a supervisor restart. Tests: region_define rewritten to assert the ListingView path + segment-wide listing + p-upgrade; new listing_view scenario (data-segment listing renders data heads, cursor reports head ea, backslash->hex->back). Pilot 115 pass / 1 pre-existing flaky (filter); rpc_smoke 29/0; test_domain 27/0.
2026-07-22app: non-function regions — open a flat listing + c/p/u edit verbs (M0)blasty
Navigating to an address not inside a function no longer refuses ("no function contains X"): _do_navigate now opens a region view (flat listing anchored at the EA, forced to disasm since there's no pseudocode). The server's no-function disasm already walks heads to the segment end, so DisasmModel is reused as-is; NavEntry gains is_region. Adds the IDA c/p/u structure-edit verbs on the disasm view: c = define_code (undefine-first, since create_insn won't carve a live item) p = define_func u = undefine wired via EditItemRequested -> _do_edit_item worker -> Program.define_code/ define_func/undefine. define_func upgrades the region to a real function view in place. New Program.bump_items() invalidates the item/function structure caches (disasm blocks, decomp, function indices + name gen) — broader than bump_names(), which only covers renames. Pilot: new `region_define` scenario undefines a small function, navigates to the bare region via the 'g' prompt (asserts it opens, not refused), then 'p' recreates the function and upgrades the view; restores the IDB in a finally. 107 passed / 1 pre-existing flaky (filter, fails on baseline).
2026-07-11TODO: opcodes in disas view doneblasty
2026-07-11disasm: render opcode bytes (toggle 'o'), padded to widest insnblasty
Fetch per-instruction opcode bytes alongside the disasm listing and show them in a column between the address and the mnemonic. Instruction length comes from consecutive addresses (variable-length safe: x86 movabs shows its full 10 bytes); the block over-fetches one instruction for the last line's boundary, falling back to the function end for the final insn. The column pads to the widest instruction across the whole function: _fetch_block tracks a running max, and on load a background scan_bytes() settles a stable width so padding doesn't jump as blocks stream in. _op_field is shared by the rendered strip, _line_plain, and search _fmt so cursor/match offsets stay aligned. 'o' toggles the column.
2026-07-09fix input visibility (search/rename) + refresh stale names across historyblasty
- 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.
2026-07-09preserve scroll offset in jump history (both views)blasty
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.