<feed xmlns='http://www.w3.org/2005/Atom'>
<title>ida-tui.git/idatui/app.py, branch pre-codemode</title>
<subtitle>tui for headless ida</subtitle>
<id>https://git.sl0p.foo/ida-tui.git/atom/idatui/app.py?h=pre-codemode</id>
<link rel='self' href='https://git.sl0p.foo/ida-tui.git/atom/idatui/app.py?h=pre-codemode'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/'/>
<updated>2026-08-07T02:09:48Z</updated>
<entry>
<title>Highlight ranges are computed per line on demand instead of for every match. Searching one character over bash matches 177k lines at 310k places, and all but the forty on screen were built and thrown away. _MatchRanges keeps the line SET eagerly and works out the offsets when a line is painted or the cursor lands on it; the blob scan now also skips to the next line after a hit.</title>
<updated>2026-08-07T02:09:48Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-07T02:09:48Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=7683ca3db981b34fb014c2e96ffa4197ccdf0383'/>
<id>urn:sha1:7683ca3db981b34fb014c2e96ffa4197ccdf0383</id>
<content type='text'>
Result: {"status":"keep","total_ms":17829.9,"lg_boot_ms":724.4,"lg_decomp_ms":2353.5,"lg_graph_ms":1041,"lg_hex_ms":434.9,"lg_index_ms":95.5,"lg_listing_cold_ms":545.6,"lg_listing_warm_ms":407.9,"lg_nav_ms":6922,"lg_palette_ms":4.8,"lg_render_ms":218.1,"lg_search_ms":884.3,"pure_graph_ms":213.6,"sm_boot_ms":432,"sm_decomp_ms":1250.2,"sm_graph_ms":687.4,"sm_hex_ms":457.5,"sm_index_ms":2.6,"sm_listing_cold_ms":265.4,"sm_listing_warm_ms":288.8,"sm_nav_ms":303.2,"sm_palette_ms":0.3,"sm_render_ms":253.9,"sm_search_ms":43,"fails":0}
</content>
</entry>
<entry>
<title>Three targeted cuts: the graph's transposition pass counts keep and swap in one pass over the neighbour pairs (was four _pair_cross calls); the barycentre median answers degree 1 and 2 without sorting; and the search body is built from windowed model reads instead of one locked row lookup per line.</title>
<updated>2026-08-07T01:28:55Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-07T01:28:55Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=98b3b98eb1bf8f195ef4ebb1375c9f161d758c17'/>
<id>urn:sha1:98b3b98eb1bf8f195ef4ebb1375c9f161d758c17</id>
<content type='text'>
Result: {"status":"keep","total_ms":17944.4,"lg_boot_ms":703.7,"lg_decomp_ms":2453.7,"lg_graph_ms":1000,"lg_hex_ms":478,"lg_index_ms":96,"lg_listing_cold_ms":453,"lg_listing_warm_ms":411.2,"lg_nav_ms":6555.9,"lg_palette_ms":4.9,"lg_render_ms":218.4,"lg_search_ms":1308.8,"pure_graph_ms":212.3,"sm_boot_ms":432.4,"sm_decomp_ms":1267.8,"sm_graph_ms":742.7,"sm_hex_ms":440,"sm_index_ms":2.5,"sm_listing_cold_ms":264.3,"sm_listing_warm_ms":289.5,"sm_nav_ms":305.4,"sm_palette_ms":0.3,"sm_render_ms":258.6,"sm_search_ms":45,"fails":0}
</content>
</entry>
<entry>
<title>Re-run of #18 (_CellRow slice assignment, one-string box borders, memoised Style sum), confirming it: 17590 -&gt; 17501. lg_graph averages 951 over the two runs against 1136 before.</title>
<updated>2026-08-07T01:05:33Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-07T01:05:33Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=ba96500478165710ab027522a685308357049175'/>
<id>urn:sha1:ba96500478165710ab027522a685308357049175</id>
<content type='text'>
Result: {"status":"keep","total_ms":17501.4,"lg_boot_ms":711,"lg_decomp_ms":2484.8,"lg_graph_ms":1017.2,"lg_hex_ms":431.9,"lg_index_ms":98.7,"lg_listing_cold_ms":528,"lg_listing_warm_ms":409.2,"lg_nav_ms":6488.2,"lg_palette_ms":4.7,"lg_render_ms":214.8,"lg_search_ms":1473.5,"pure_graph_ms":244.3,"sm_boot_ms":451.4,"sm_decomp_ms":596,"sm_graph_ms":691.3,"sm_hex_ms":444.5,"sm_index_ms":0,"sm_listing_cold_ms":263,"sm_listing_warm_ms":284.2,"sm_nav_ms":374.7,"sm_palette_ms":0.3,"sm_render_ms":252.1,"sm_search_ms":37.5,"fails":0}
</content>
</entry>
<entry>
<title>Re-run of #16 (keep the joined search body across a cancelled search and across navigation inside the same segment), confirming it. total 17784 -&gt; 17590; lg_search 1917 -&gt; 1400, sm_search 70 -&gt; 48. Also lands .auto/check_search.py in the checks gate: it compares both search fast paths against the plain per-line loop for every typed prefix.</title>
<updated>2026-08-07T00:56:22Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-07T00:56:22Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=e41538763c6d2465c564e49ffcfce599836d8df7'/>
<id>urn:sha1:e41538763c6d2465c564e49ffcfce599836d8df7</id>
<content type='text'>
Result: {"status":"keep","total_ms":17589.8,"lg_boot_ms":710.6,"lg_decomp_ms":2404.9,"lg_graph_ms":1136.1,"lg_hex_ms":425.6,"lg_index_ms":103.4,"lg_listing_cold_ms":417.2,"lg_listing_warm_ms":508.1,"lg_nav_ms":6553.8,"lg_palette_ms":4.8,"lg_render_ms":215.7,"lg_search_ms":1400.3,"pure_graph_ms":239.3,"sm_boot_ms":443.2,"sm_decomp_ms":668.4,"sm_graph_ms":698.1,"sm_hex_ms":443.6,"sm_index_ms":0,"sm_listing_cold_ms":267.3,"sm_listing_warm_ms":286,"sm_nav_ms":356.6,"sm_palette_ms":0.3,"sm_render_ms":258.6,"sm_search_ms":47.9,"fails":0}
</content>
</entry>
<entry>
<title>HexView.render_line emits style RUNS instead of one Segment per byte cell (35 -&gt; 7 segments per row), and Head is a NamedTuple rather than a frozen dataclass (tuple.__new__ 1.9us vs a dataclass __init__ 2.9us, and it is built once per listing row walked).</title>
<updated>2026-08-07T00:49:28Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-07T00:49:28Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=625b067ed89f773bf1601d4dcb01484826db34c0'/>
<id>urn:sha1:625b067ed89f773bf1601d4dcb01484826db34c0</id>
<content type='text'>
Result: {"status":"keep","total_ms":17784.1,"lg_boot_ms":680.8,"lg_decomp_ms":2344,"lg_graph_ms":1106.5,"lg_hex_ms":576.4,"lg_index_ms":96.5,"lg_listing_cold_ms":552.9,"lg_listing_warm_ms":431.3,"lg_nav_ms":6256.4,"lg_palette_ms":4.8,"lg_render_ms":224.1,"lg_search_ms":1916.5,"pure_graph_ms":241.3,"sm_boot_ms":442.6,"sm_decomp_ms":591.3,"sm_graph_ms":663.9,"sm_hex_ms":419.2,"sm_index_ms":0,"sm_listing_cold_ms":258.2,"sm_listing_warm_ms":281.8,"sm_nav_ms":374.4,"sm_palette_ms":0.3,"sm_render_ms":251.1,"sm_search_ms":69.8,"fails":0}
</content>
</entry>
<entry>
<title>Search the whole segment as ONE joined string. Every line is concatenated once (with a start-offset table) so finding a term is a C-level str.find walk instead of a python loop that rebuilds and case-folds 224k lines per keystroke. Falls back to the per-line loop if case-folding changes the string's length.</title>
<updated>2026-08-07T00:41:31Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-07T00:41:31Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=26869018f61878446cd9fb9422be07493f36fca9'/>
<id>urn:sha1:26869018f61878446cd9fb9422be07493f36fca9</id>
<content type='text'>
Result: {"status":"keep","total_ms":18618.9,"lg_boot_ms":689.6,"lg_decomp_ms":2560,"lg_graph_ms":1035.1,"lg_hex_ms":684.2,"lg_index_ms":100.4,"lg_listing_cold_ms":551.1,"lg_listing_warm_ms":397.8,"lg_nav_ms":6687.8,"lg_palette_ms":4.7,"lg_render_ms":211.9,"lg_search_ms":1858.2,"pure_graph_ms":238.5,"sm_boot_ms":431.4,"sm_decomp_ms":671.2,"sm_graph_ms":728.1,"sm_hex_ms":552,"sm_index_ms":0,"sm_listing_cold_ms":257.4,"sm_listing_warm_ms":281,"sm_nav_ms":365.9,"sm_palette_ms":0.3,"sm_render_ms":242.8,"sm_search_ms":69.6,"fails":0}
</content>
</entry>
<entry>
<title>Only re-apply a scroll after the next refresh when it actually clamped. Both _apply_scroll implementations unconditionally scheduled a deferred scroll_to + refresh(layout=True) — a whole-screen re-arrange on every scroll — as a workaround for scrolling before the view's size is computed. Now the deferred pass runs only when scroll_offset didn't reach the target.</title>
<updated>2026-08-07T00:16:16Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-07T00:16:16Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=f8fb9b7c7b6abfd495ed3471b24c8ccd51187197'/>
<id>urn:sha1:f8fb9b7c7b6abfd495ed3471b24c8ccd51187197</id>
<content type='text'>
Result: {"status":"keep","total_ms":19005.8,"lg_boot_ms":788.9,"lg_decomp_ms":2752.5,"lg_graph_ms":899,"lg_hex_ms":565.2,"lg_index_ms":71.9,"lg_listing_cold_ms":416.1,"lg_listing_warm_ms":402.3,"lg_nav_ms":6756.3,"lg_palette_ms":4.7,"lg_render_ms":217.1,"lg_search_ms":2335.8,"pure_graph_ms":235.3,"sm_boot_ms":537.4,"sm_decomp_ms":600.9,"sm_graph_ms":693.1,"sm_hex_ms":549.9,"sm_index_ms":0,"sm_listing_cold_ms":280.9,"sm_listing_warm_ms":261.2,"sm_nav_ms":309,"sm_palette_ms":0.3,"sm_render_ms":249.3,"sm_search_ms":78.7,"fails":0}
</content>
</entry>
<entry>
<title>Two hot-path fixes found by profiling the plain-line builder: the opcode-bytes column used a per-byte f-string generator where bytes.hex(' ').upper() does it in one C call (12x), and ListingModel._phys/_head_index_at re-imported bisect on every call. _line_plain 2.64 -&gt; 1.57 us/row.</title>
<updated>2026-08-07T00:12:27Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-07T00:12:27Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=414873898f236cdc1ab0d7c6a83f1a58f57f2d6b'/>
<id>urn:sha1:414873898f236cdc1ab0d7c6a83f1a58f57f2d6b</id>
<content type='text'>
Result: {"status":"keep","total_ms":19476.3,"lg_boot_ms":756.3,"lg_decomp_ms":2495.1,"lg_graph_ms":946,"lg_hex_ms":905.4,"lg_index_ms":72,"lg_listing_cold_ms":547.5,"lg_listing_warm_ms":410.4,"lg_nav_ms":6723.4,"lg_palette_ms":5,"lg_render_ms":218.8,"lg_search_ms":2312.4,"pure_graph_ms":239.5,"sm_boot_ms":535.3,"sm_decomp_ms":616.4,"sm_graph_ms":682.2,"sm_hex_ms":824.9,"sm_index_ms":0,"sm_listing_cold_ms":258.9,"sm_listing_warm_ms":261,"sm_nav_ms":339.5,"sm_palette_ms":0.3,"sm_render_ms":248.7,"sm_search_ms":77.3,"fails":0}
</content>
</entry>
<entry>
<title>Incremental search narrows instead of rescanning. Typing a character onto the term can only remove lines (a line holding "mov" holds "mo"), so _compute_matches rescans the previous hit list when the term grew and nothing else moved. Keyed on (term, case-fold, row count, line-source id) so a listing still streaming rows in behind the search falls back to a full scan.</title>
<updated>2026-08-07T00:00:06Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-07T00:00:06Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=5045ba1747574165cc18de6b5f7f28a6e6baa711'/>
<id>urn:sha1:5045ba1747574165cc18de6b5f7f28a6e6baa711</id>
<content type='text'>
Result: {"status":"keep","total_ms":20835.7,"lg_boot_ms":754.4,"lg_decomp_ms":2593.7,"lg_graph_ms":929.5,"lg_hex_ms":1080.2,"lg_index_ms":76.1,"lg_listing_cold_ms":526.7,"lg_listing_warm_ms":410.1,"lg_nav_ms":6674.8,"lg_palette_ms":5,"lg_render_ms":225.2,"lg_search_ms":3310.3,"pure_graph_ms":239.3,"sm_boot_ms":537.9,"sm_decomp_ms":630.1,"sm_graph_ms":734.3,"sm_hex_ms":869.9,"sm_index_ms":0,"sm_listing_cold_ms":260.8,"sm_listing_warm_ms":262.3,"sm_nav_ms":334.8,"sm_palette_ms":0.3,"sm_render_ms":268.4,"sm_search_ms":111.6,"fails":0}
</content>
</entry>
<entry>
<title>app: the view mode is a type, and 'disasm' is gone</title>
<updated>2026-08-06T22:07:22Z</updated>
<author>
<name>blasty</name>
<email>blasty@local</email>
</author>
<published>2026-08-06T22:07:22Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=cefdeb88ab3271313db741c9c95677178b18d484'/>
<id>urn:sha1:cefdeb88ab3271313db741c9c95677178b18d484</id>
<content type='text'>
_active was a bare string with 49 comparisons across four modules and a fifth
value nobody meant to keep. "disasm" was assigned on exactly one path -- a
decompile that failed with nowhere to return to -- and named the same widget as
"listing". Four sites understood it; five compared against "listing" alone and
silently took the wrong branch:

  * Tab out of a failed decompile set "listing" instead of "decomp", so the
    first press appeared to do nothing.
  * rpc.py carried a workaround for a mode change that never arrived, keyed on
    being ALREADY in the ghost state -- so it fired in the rare case and not in
    the common one. Now keyed on LISTING, which is the case that happens.
  * drive.py asked the socket to show it "disasm", a value the app will now
    never report, and would have toggled twice and given up.

ViewMode is a StrEnum on purpose: _active goes straight to drivers as
cursor.kind and the pilot compares it to plain strings, so members being strings
keeps every payload and comparison working. What it buys is one place that says
which modes exist, and an AttributeError instead of silence on a typo.

Read it through is_listing/is_decomp/is_hex/is_graph/in_code rather than ==.
The bare comparisons are what let the ghost hide, and they are what the next
mode would have to hunt down -- adding "graph" already cost one crash that way
(_active_code_view returning None when a prompt closed).

view_modes_all_handled walks the enum and asks the app the questions it asks
itself. Verified it bites: adding a fifth unhandled member fails it twice.

746 checks, 142.3s.
</content>
</entry>
</feed>
