aboutsummaryrefslogtreecommitdiffstats
path: root/idatui/domain.py
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-09 12:05:24 +0200
committerblasty <blasty@local>2026-07-09 12:05:24 +0200
commit8d67549eaa927c7ffc48c57f3ebc438c0fe7da09 (patch)
treea310556afc303c444f184fdc8fca28843e19f54d /idatui/domain.py
parentkeep idle workers alive: bump_idle_ttl + KeepAlive heartbeat (diff)
downloadida-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.py34
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)