diff options
| author | blasty <blasty@local> | 2026-07-09 12:05:24 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-09 12:05:24 +0200 |
| commit | 8d67549eaa927c7ffc48c57f3ebc438c0fe7da09 (patch) | |
| tree | a310556afc303c444f184fdc8fca28843e19f54d /idatui/domain.py | |
| parent | keep idle workers alive: bump_idle_ttl + KeepAlive heartbeat (diff) | |
| download | ida-tui-8d67549eaa927c7ffc48c57f3ebc438c0fe7da09.tar.gz ida-tui-8d67549eaa927c7ffc48c57f3ebc438c0fe7da09.tar.xz ida-tui-8d67549eaa927c7ffc48c57f3ebc438c0fe7da09.zip | |
Phase 1 TUI: two-pane functions<->disasm, virtualized listing, nav backbone
- 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
Diffstat (limited to 'idatui/domain.py')
| -rw-r--r-- | idatui/domain.py | 34 |
1 files changed, 34 insertions, 0 deletions
diff --git a/idatui/domain.py b/idatui/domain.py index d19a8a3..859c883 100644 --- a/idatui/domain.py +++ b/idatui/domain.py @@ -141,6 +141,10 @@ class FunctionIndex: self._done = True return len(data) + def load_next_page(self) -> int: + """Load one more page; return the number of rows fetched (0 at end).""" + return self._load_next_page() + def ensure(self, n: int) -> None: """Ensure at least ``n`` functions are loaded (or all, if fewer exist).""" while not self._done and len(self._funcs) < n: @@ -262,6 +266,36 @@ class DisasmModel: self._prefetch_block(b0 - 1) # backward scroll return out + def cached_line(self, idx: int) -> Line | None: + """Non-blocking single-line peek: return the cached Line or None. Never + touches the network — used by the virtualized view's render path.""" + if idx < 0: + return None + b = idx // self.BLOCK + off = idx - b * self.BLOCK + with self._lock: + block = self._blocks.get(b) + if block is None or off >= len(block): + return None + return block[off] + + def is_cached(self, start: int, count: int) -> bool: + """True if every block covering [start, start+count) is already cached.""" + if count <= 0: + return True + b0, b1 = start // self.BLOCK, (start + count - 1) // self.BLOCK + with self._lock: + return all(b in self._blocks for b in range(b0, b1 + 1)) + + def ensure_async(self, start: int, count: int) -> None: + """Schedule background fetches for any missing blocks in the range + (non-blocking). Safe to call every render.""" + if count <= 0: + return + b0, b1 = start // self.BLOCK, (start + count - 1) // self.BLOCK + for b in range(b0, b1 + 1): + self._prefetch_block(b) + def cached_blocks(self) -> int: with self._lock: return len(self._blocks) |
