aboutsummaryrefslogtreecommitdiffstats
path: root/idatui
diff options
context:
space:
mode:
Diffstat (limited to 'idatui')
-rw-r--r--idatui/__init__.py6
-rw-r--r--idatui/app.py42
-rw-r--r--idatui/domain.py28
-rw-r--r--idatui/errors.py2
-rw-r--r--idatui/launch.py16
-rw-r--r--idatui/nexus_client.py (renamed from idatui/codemode_client.py)88
-rw-r--r--idatui/pane.py18
-rw-r--r--idatui/pool.py14
-rw-r--r--idatui/project.py14
-rw-r--r--idatui/remote_ops.py4
-rw-r--r--idatui/remote_tools.py12
11 files changed, 121 insertions, 123 deletions
diff --git a/idatui/__init__.py b/idatui/__init__.py
index 7da28b3..e89d8f6 100644
--- a/idatui/__init__.py
+++ b/idatui/__init__.py
@@ -1,4 +1,4 @@
-"""idatui — a keyboard-first TUI using shared IDA Code Mode databases."""
+"""idatui — a keyboard-first TUI using shared IDA Nexus databases."""
from .errors import (
IDAError,
@@ -10,7 +10,7 @@ from .errors import (
IDASessionError,
Session,
)
-from .codemode_client import CodeModeClient
+from .nexus_client import NexusClient
from .domain import (
Program,
FunctionIndex,
@@ -25,7 +25,7 @@ from .domain import (
)
__all__ = [
- "CodeModeClient",
+ "NexusClient",
"Program",
"FunctionIndex",
"DisasmModel",
diff --git a/idatui/app.py b/idatui/app.py
index e4c2936..05b428a 100644
--- a/idatui/app.py
+++ b/idatui/app.py
@@ -10,7 +10,7 @@ Design notes:
without ever materializing 52k lines in a widget.
* All network/domain work runs in Textual worker threads; the UI never blocks.
* An address-history stack backs Enter (follow) / Esc (back), IDA-style.
-* Database lifecycle is lease-based through ida_codemode: matching GUI sessions
+* Database lifecycle is lease-based through ida_nexus: matching GUI sessions
are reused, otherwise a shared managed idalib worker is opened on demand.
"""
@@ -55,7 +55,7 @@ from .highlight import CTextArea, highlight_c
from .journal import Journal
from .errors import IDAConnectionError
-from .codemode_client import CodeModeClient, registered_database
+from .nexus_client import NexusClient, registered_database
from .domain import Func, Head, ListingModel, Program, Struct
# Styles for the disassembly listing.
@@ -1087,7 +1087,7 @@ class ListingView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=Tru
def _span_segments(h: Head, fallback: Style):
"""Segments for a row's disassembly text.
- Uses IDA's own token classification when Code Mode supplies it; falls
+ Uses IDA's own token classification when IDA Nexus supplies it; falls
back to the mnemonic/rest split when spans are absent or disagree with
the plain text.
"""
@@ -5238,7 +5238,7 @@ class IdaTui(App):
self._open_path = open_path
self._ttl = ttl
self._load_args = load_args or "" # first-open options for a headerless blob
- self._new_database = False # Ctrl+L asks Code Mode for a fresh IDB
+ self._new_database = False # Ctrl+L asks IDA Nexus for a fresh IDB
self._title = (os.path.basename(open_path) if open_path else "")
#: Where we are in the execution trace, and everything that moves us.
#: Owns the trace state; the _trace/_t/_trail_* properties below
@@ -5247,7 +5247,7 @@ class IdaTui(App):
self._do_keepalive = keepalive
self._rpc_path = rpc_path
self._rpc = None
- self.client: CodeModeClient | None = None
+ self.client: NexusClient | None = None
self.program: Program | None = None
self._loading_screen: LoadingScreen | None = None
self._ka = None
@@ -5288,7 +5288,7 @@ class IdaTui(App):
self.journal = Journal()
self._xref_focus_name: str | None = None
self._dirty = False
- # One subscription for the active database. CodeModeClient debounces
+ # One subscription for the active database. NexusClient debounces
# bursts off the Textual worker pool; the callback re-enters here on the
# UI thread to invalidate and reload the visible models.
self._idb_event_watch = None
@@ -5362,7 +5362,7 @@ class IdaTui(App):
if self._rpc_path:
self._start_rpc()
# A file no loader recognises has to be described before it can be
- # opened, so ask BEFORE Code Mode creates it — once IDA has made a database
+ # opened, so ask BEFORE IDA Nexus creates it — once IDA has made a database
# the answer is baked in and changing it requires a fresh-IDB reopen.
if self._project is not None:
ref = self._pending_load_ref()
@@ -5435,7 +5435,7 @@ class IdaTui(App):
ref = self._project.by_label(self._binary)
if ref is not None:
path, label = ref.source, ref.label
- # Release our lease first. Code Mode waits for a managed worker's final
+ # Release our lease first. IDA Nexus waits for a managed worker's final
# lease grace, then creates the replacement IDB atomically. A GUI-backed
# database is rejected by _can_reload(): the TUI must never close it.
self._release_database()
@@ -5634,7 +5634,7 @@ class IdaTui(App):
return len(text)
# -- live refresh from shared IDB changes ----------------------------- #
- def _start_idb_event_watch(self, client: CodeModeClient) -> None:
+ def _start_idb_event_watch(self, client: NexusClient) -> None:
self._stop_idb_event_watch()
watch = getattr(client, "watch_idb_events", None)
if watch is None: # IDA-free test doubles and pre-event adapters
@@ -5661,7 +5661,7 @@ class IdaTui(App):
watcher.close()
def _idb_event_watch_failed(
- self, client: CodeModeClient, error: BaseException
+ self, client: NexusClient, error: BaseException
) -> None:
if client is not self.client:
return
@@ -5685,7 +5685,7 @@ class IdaTui(App):
return anchor
def _refresh_idb_events(
- self, client: CodeModeClient, events: tuple[dict, ...]
+ self, client: NexusClient, events: tuple[dict, ...]
) -> None:
"""Invalidate once per external edit burst and reload the active surface."""
program = self.program
@@ -5768,7 +5768,7 @@ class IdaTui(App):
# -- connection loss / recovery --------------------------------------- #
def _handle_exception(self, error: BaseException) -> None:
- """Intercept a lost Code Mode lease so the app can rediscover the DB.
+ """Intercept a lost IDA Nexus lease so the app can rediscover the DB.
Everything unrelated to database connectivity crashes as usual.
"""
@@ -5812,11 +5812,11 @@ class IdaTui(App):
return
if self._project is not None and self._binary is not None:
ref = self._project.by_label(self._binary)
- client = CodeModeClient(
+ client = NexusClient(
ref.staged, ttl=self._ttl, load_args=ref.load_args,
output_database=ref.db, spawn=False)
else:
- client = CodeModeClient(
+ client = NexusClient(
self._open_path, ttl=self._ttl,
load_args=self._load_args, spawn=False)
client.connect(progress=lambda m: self.app.call_from_thread(
@@ -5826,7 +5826,7 @@ class IdaTui(App):
return
self.app.call_from_thread(self._after_reconnect, client, Program(client))
- def _after_reconnect(self, client: "CodeModeClient", program: "Program") -> None:
+ def _after_reconnect(self, client: "NexusClient", program: "Program") -> None:
old_client, old_program = self.client, self.program
self._stop_idb_event_watch()
if old_program is not None:
@@ -5886,7 +5886,7 @@ class IdaTui(App):
self._load_functions()
def _open_database_client(self): # type: ignore[no-untyped-def]
- """Attach through Code Mode, reusing a GUI or managed idalib database."""
+ """Attach through IDA Nexus, reusing a GUI or managed idalib database."""
if self._pool is not None: # project mode: the pool owns the leases
label = self._binary or self._project.refs[0].label
client = self._pool.get(label, progress=lambda m:
@@ -5898,13 +5898,13 @@ class IdaTui(App):
return client
if not self._open_path:
self.app.call_from_thread(
- self._status, "Code Mode needs a database or executable path")
+ self._status, "IDA Nexus needs a database or executable path")
self.app.call_from_thread(self._dismiss_loading)
return None
base = os.path.basename(self._open_path)
self.app.call_from_thread(
- self._status, f"discovering Code Mode database for {base}…")
- client = CodeModeClient(self._open_path, ttl=self._ttl,
+ self._status, f"discovering IDA Nexus database for {base}…")
+ client = NexusClient(self._open_path, ttl=self._ttl,
load_args=self._load_args,
new_database=self._new_database)
client.connect(progress=lambda m: self.app.call_from_thread(
@@ -5978,7 +5978,7 @@ class IdaTui(App):
@work(thread=True, exclusive=True, group="index")
def _index_binary(self) -> None:
"""Fold this binary's symbols + strings into the project index, so it can
- be searched later even when its Code Mode lease is gone."""
+ be searched later even when its IDA Nexus lease is gone."""
if self._index is None or self._project is None or self._binary is None:
return
ref = self._project.by_label(self._binary)
@@ -6081,7 +6081,7 @@ class IdaTui(App):
cursor=0, push=True, is_region=True)
def _can_reload(self) -> bool:
- """Whether Code Mode can replace this IDB with different options.
+ """Whether IDA Nexus can replace this IDB with different options.
A GUI database is owned by the user and has no remote close/rollback
route. Managed idalib databases can be released and reopened fresh.
diff --git a/idatui/domain.py b/idatui/domain.py
index b042332..1e5a863 100644
--- a/idatui/domain.py
+++ b/idatui/domain.py
@@ -1,4 +1,4 @@
-"""Domain / paging layer: address-centric models over IDA Code Mode.
+"""Domain / paging layer: address-centric models over IDA Nexus.
This is where the "millions of lines" problem is solved, so the TUI widgets only
ever see a viewport-sized slice. Every hard-won constraint from
@@ -7,7 +7,7 @@ ever see a viewport-sized slice. Every hard-won constraint from
* Page sizes remain bounded so remote execution returns viewport-scale JSON.
* Pagination advances by the number of rows actually returned.
* Deep head walks are block-cached (revisits are free) and neighboring blocks
- prefetch through the thread-safe Code Mode client.
+ prefetch through the thread-safe IDA Nexus client.
* Expensive function totals are fetched once and cached.
* Decompilation failures are surfaced as data, not application crashes.
@@ -31,7 +31,7 @@ from . import remote_ops
from .errors import IDAToolError
if TYPE_CHECKING: # type hint only
- from .codemode_client import CodeModeClient
+ from .nexus_client import NexusClient
# Clamps derived from measured caps (list ~700, disasm ~500). Margin included.
LIST_PAGE = 500
@@ -90,7 +90,7 @@ class Line:
class Head(NamedTuple):
- """One flat-listing item (from the Code Mode ``heads`` operation): a code
+ """One flat-listing item (from the IDA Nexus ``heads`` operation): a code
instruction, a data item, or an undefined byte run.
A ``NamedTuple`` rather than a dataclass because this is by far the
@@ -111,7 +111,7 @@ class Head(NamedTuple):
name: str | None = None
raw: bytes | None = None # opcode/item bytes (filled in for code by the model)
#: [(kind, text)] from IDA's own colour tags — mnem/reg/num/name/str/punct/…
- #: None when Code Mode didn't provide them (or the spans
+ #: None when IDA Nexus didn't provide them (or the spans
#: disagreed with the plain text, in which case the text wins).
#:
#: Held exactly as it came off the wire, and **read-only**. The worker
@@ -693,7 +693,7 @@ class ListingModel:
"""A flat, IDA-style disassembly *listing* over one segment: code, data and
undefined heads interleaved, unlike ``DisasmModel`` (one function, code only).
- Backed by the Code Mode adapter's ``heads`` operation, which walks item heads
+ Backed by the IDA Nexus adapter's ``heads`` operation, which walks item heads
and renders each via ``generate_disasm_line``. The segment is walked lazily in
forward pages (``FunctionIndex`` style); line index == position in the walked
head list. Random access to an address is O(distance-from-seg-start) the
@@ -701,7 +701,7 @@ class ListingModel:
on demand as the viewport scrolls. Synchronous + thread-safe.
"""
- PAGE = 500 # viewport-scale heads per Code Mode execution
+ PAGE = 500 # viewport-scale heads per IDA Nexus execution
#: Generation marker for a skeleton (text-less) page. Never equals a real
#: _text_gen, which counts up from 0, so such a page always reads as stale.
_SKELETON_GEN = -1
@@ -1434,7 +1434,7 @@ class HexModel:
class Program:
"""The bound analysis session: models, caches, and a small prefetch pool."""
- def __init__(self, client: "CodeModeClient", prefetch_workers: int = 2):
+ def __init__(self, client: "NexusClient", prefetch_workers: int = 2):
self.client = client
self._pool = ThreadPoolExecutor(
max_workers=prefetch_workers, thread_name_prefix="idatui-prefetch"
@@ -1485,7 +1485,7 @@ class Program:
"""Sorted raw segment map [(start, end, file_off, name)] — the single
source for sections()/file_regions()/image_range. Cached.
- Uses the Code Mode adapter's ``file_regions`` operation (a plain segment
+ Uses the IDA Nexus adapter's ``file_regions`` operation (a plain segment
walk, ~ms), avoiding broad binary surveys on the hex-pane open path.
"""
if self._segments_cache is not None:
@@ -1573,7 +1573,7 @@ class Program:
def read_bytes(self, ea: int, n: int) -> bytes:
"""Raw bytes [ea, ea+n) from IDA (gaps read as zero).
- The Code Mode adapter returns one contiguous hex string (C-speed in IDA).
+ The IDA Nexus adapter returns one contiguous hex string (C-speed in IDA).
A legacy ``get_bytes`` decoding fallback remains for alternate clients.
"""
if n <= 0:
@@ -1778,7 +1778,7 @@ class Program:
except IDAToolError as e:
msg = e.message
if "not found" in msg.lower() and "del_type" in msg:
- return "the connected Code Mode runtime cannot delete local types"
+ return "the connected IDA Nexus runtime cannot delete local types"
return msg
# -- disassembly ------------------------------------------------------- #
@@ -1795,7 +1795,7 @@ class Program:
"""Drop local and Hex-Rays caches before an explicit view refresh.
Normal edit paths use generation-based invalidation. Ctrl+R is also for
- changes made by another Code Mode/IDA client, for which this Program has
+ changes made by another IDA Nexus/IDA client, for which this Program has
seen no generation bump, so it must explicitly ask Hex-Rays to discard
its cached cfunc.
"""
@@ -1809,7 +1809,7 @@ class Program:
pass
def decompile(self, ea: int, refresh: bool = False) -> Decompilation:
- """Full pseudocode for a function, returned directly by Code Mode."""
+ """Full pseudocode for a function, returned directly by IDA Nexus."""
if not refresh:
with self._lock:
hit = self._decomp.get(ea)
@@ -2011,7 +2011,7 @@ class Program:
def define_func(self, ea: int) -> dict:
"""Create a function starting at ``ea`` (IDA's 'p').
- Prefers the Code Mode operation, which works out the end when IDA can't;
+ Prefers the IDA Nexus operation, which works out the end when IDA can't;
falls back to a plain create for alternate clients.
"""
try:
diff --git a/idatui/errors.py b/idatui/errors.py
index 7632ade..ab1365a 100644
--- a/idatui/errors.py
+++ b/idatui/errors.py
@@ -1,6 +1,6 @@
"""TUI-facing error hierarchy and lightweight database session model.
-The Code Mode adapter normalizes ``ida_codemode`` transport and execution
+The IDA Nexus adapter normalizes ``ida_nexus`` transport and execution
errors into these types so the domain and Textual layers do not depend on HTTP or
registry implementation details.
"""
diff --git a/idatui/launch.py b/idatui/launch.py
index e3cb091..a0201a7 100644
--- a/idatui/launch.py
+++ b/idatui/launch.py
@@ -1,6 +1,6 @@
-"""One-shot launcher for the IDA Code Mode-backed TUI.
+"""One-shot launcher for the IDA Nexus-backed TUI.
-A path first resolves to a registered GUI database; when none matches, Code Mode
+A path first resolves to a registered GUI database; when none matches, IDA Nexus
reuses or starts a managed idalib worker. With no path, a single registered
database is selected automatically.
@@ -34,9 +34,9 @@ def _log(msg: str) -> None:
def _registered_databases() -> tuple[list[dict], list[dict]]:
- """Ready and blocked Code Mode registrations, with normalized errors."""
+ """Ready and blocked IDA Nexus registrations, with normalized errors."""
try:
- from ida_codemode import InstanceState, discover_databases
+ from ida_nexus import InstanceState, discover_databases
ready: list[dict] = []
blocked: list[dict] = []
@@ -70,7 +70,7 @@ def main(argv: list[str] | None = None) -> int:
help="open a multi-binary project (created from the given "
"binaries if FILE doesn't exist)")
p.add_argument("--ttl", type=int, default=1800,
- help="deprecated compatibility option (Code Mode uses leases)")
+ help="deprecated compatibility option (IDA Nexus uses leases)")
p.add_argument("--no-keepalive", action="store_true",
help="deprecated compatibility option (the lease is the heartbeat)")
p.add_argument("--rpc", metavar="PATH",
@@ -87,7 +87,7 @@ def main(argv: list[str] | None = None) -> int:
g.add_argument("--base", metavar="ADDR",
help="load address, e.g. 0x8000000 (any base; NOT paragraphs)")
g.add_argument("--ida-args", metavar="STR", dest="ida_args",
- help="legacy switches; only Code Mode-representable -p/-b/-T are accepted")
+ help="legacy switches; only IDA Nexus-representable -p/-b/-T are accepted")
args = p.parse_args(argv)
load: dict = {}
@@ -166,10 +166,10 @@ def main(argv: list[str] | None = None) -> int:
_log(f"attaching to registered {item.get('backend')} database: {binary}")
elif not ready:
detail = f" ({blocked[0].get('error')})" if blocked else ""
- _log(f"no registered Code Mode database; pass a binary path{detail}")
+ _log(f"no registered IDA Nexus database; pass a binary path{detail}")
return 2
else:
- _log("several Code Mode databases are registered; pass one of these paths:")
+ _log("several IDA Nexus databases are registered; pass one of these paths:")
for item in ready:
_log(f" {item.get('exe_path') or item.get('idb_path')} "
f"[{item.get('backend')}, {item.get('record_id')}]")
diff --git a/idatui/codemode_client.py b/idatui/nexus_client.py
index e95238f..44eb1b1 100644
--- a/idatui/codemode_client.py
+++ b/idatui/nexus_client.py
@@ -1,4 +1,4 @@
-"""Client adapter from ida-tui's domain operations to IDA Code Mode.
+"""Client adapter from ida-tui's domain operations to IDA Nexus.
``DatabaseHandle`` is the lifecycle boundary: it discovers an already-registered
GUI database, reuses a shared managed idalib worker, or starts one when needed.
@@ -6,9 +6,9 @@ The TUI never owns or terminates an IDA process. Closing this client releases
only its lease.
Remote operations are ordinary typed Python functions declared in
-``idatui.remote_ops``. Code Mode installs their content-addressed modules once
-per handle; subsequent calls send only encoded arguments. The optimized
-IDAPython listing/decompiler implementation remains real source in
+``idatui.remote_ops``. IDA Nexus installs their content-addressed modules once
+per IDA Python interpreter; subsequent calls send only encoded arguments. The
+optimized IDAPython listing/decompiler implementation remains real source in
``idatui.remote_tools`` and is installed through the same module interface.
"""
@@ -23,55 +23,55 @@ from typing import Any
from .errors import IDAConnectionError, IDATimeoutError, IDAToolError, Session
-# ida_codemode is imported EAGERLY-IF-PRESENT but never at hard import cost.
+# ida_nexus is imported EAGERLY-IF-PRESENT but never at hard import cost.
#
# The paging/graph/trace layers and their offline test suites must keep importing
-# `idatui` on a machine with no IDA and no Code Mode installed -- that is the
+# `idatui` on a machine with no IDA and no IDA Nexus installed -- that is the
# house rule the stdlib-only worker client used to satisfy for free, and
# `tests/run.py --fast` (380 checks, any python3) depends on it. A hard top-level
# import here makes the whole package unimportable, so the failure is deferred to
# the first operation that genuinely needs the library.
-_CODEMODE_ERROR: Exception | None = None
+_NEXUS_ERROR: Exception | None = None
try:
- from ida_codemode import (
- CodeModeConnectionError,
+ from ida_nexus import (
DatabaseBusyError,
DatabaseDisconnectedError,
DatabaseHandle,
DatabaseInstance,
DatabaseOpenOptions,
+ NexusConnectionError,
RemoteError,
find_database_owner,
wait_database_released,
)
except ImportError as _exc: # library absent: usable only for offline layers
- _CODEMODE_ERROR = _exc
+ _NEXUS_ERROR = _exc
# Bound to None rather than left undefined so the names stay patchable: the
# offline contract tests inject a fake DatabaseHandle here.
- CodeModeConnectionError = DatabaseDisconnectedError = RemoteError = None # type: ignore[assignment,misc]
+ NexusConnectionError = DatabaseDisconnectedError = RemoteError = None # type: ignore[assignment,misc]
DatabaseBusyError = DatabaseHandle = DatabaseInstance = None # type: ignore[assignment,misc]
DatabaseOpenOptions = find_database_owner = wait_database_released = None # type: ignore[assignment]
-def _require_codemode() -> None:
- """Raise an actionable error when the Code Mode library is missing.
+def _require_nexus() -> None:
+ """Raise an actionable error when the IDA Nexus library is missing.
Gated on the binding, not on the original import result, so a test that
injects a fake ``DatabaseHandle`` exercises the real adapter logic.
"""
if DatabaseHandle is None:
raise IDAConnectionError(
- "ida-codemode is not installed in this environment "
- f"({_CODEMODE_ERROR}). Install it (e.g. `uv sync`, or "
- "`pip install ida-codemode`) so ida-tui can lease a "
+ "ida-nexus is not installed in this environment "
+ f"({_NEXUS_ERROR}). Install it (e.g. `uv sync`, or "
+ "`pip install ida-nexus`) so ida-tui can lease a "
"database."
- ) from _CODEMODE_ERROR
+ ) from _NEXUS_ERROR
def database_owner(idb_path: str, staged_path: str | None = None):
- """The Code Mode instance that owns ``idb_path``/``staged_path``, else None.
+ """The IDA Nexus instance that owns ``idb_path``/``staged_path``, else None.
- Returns None when the Code Mode library is absent: with no library there is
+ Returns None when the IDA Nexus library is absent: with no library there is
no client in this environment that could be holding the database, and the
IDA-free layers (project staging) must keep working. Discovery errors with
the library installed still propagate because unknown ownership is unsafe.
@@ -89,8 +89,8 @@ def database_owner(idb_path: str, staged_path: str | None = None):
def registered_database(path: str, output_database: str | None = None) -> bool:
- """Whether a live/lock-held Code Mode instance owns this target."""
- _require_codemode()
+ """Whether a live/lock-held IDA Nexus instance owns this target."""
+ _require_nexus()
return (
find_database_owner(
path,
@@ -115,9 +115,9 @@ class _NoopKeepAlive:
def _parse_load_args(value: str) -> tuple[str | None, int | None, str | None]:
- """Translate ida-tui's legacy first-open switches to Code Mode options.
+ """Translate ida-tui's legacy first-open switches to IDA Nexus options.
- Code Mode has typed options for processor, natural loading address and file
+ IDA Nexus has typed options for processor, natural loading address and file
type. It deliberately has no arbitrary command-line escape hatch; reject
switches we cannot represent instead of silently loading a blob wrongly.
"""
@@ -146,7 +146,7 @@ def _parse_load_args(value: str) -> tuple[str | None, int | None, str | None]:
if unsupported:
joined = " ".join(unsupported)
raise ValueError(
- "ida-codemode cannot represent arbitrary IDA load options: "
+ "ida-nexus cannot represent arbitrary IDA load options: "
f"{joined!r}; use processor/base/file type options instead"
)
return processor, loading_address, file_type
@@ -155,7 +155,7 @@ def _parse_load_args(value: str) -> tuple[str | None, int | None, str | None]:
class IDBEventListener:
"""Debounced, closeable delivery of another client's IDB changes.
- Code Mode's subscription is a blocking iterator, so one daemon thread reads
+ IDA Nexus's subscription is a blocking iterator, so one daemon thread reads
it and a second waits for a quiet period before handing a batch to the UI.
Keeping the debounce here avoids a permanent Textual worker (which would
make the app's worker-idle contract impossible) and bounds refresh work to
@@ -164,7 +164,7 @@ class IDBEventListener:
def __init__(
self,
- client: "CodeModeClient",
+ client: "NexusClient",
callback: Callable[[tuple[dict[str, Any], ...]], None],
*,
on_error: Callable[[BaseException], None] | None = None,
@@ -267,8 +267,8 @@ class IDBEventListener:
subscription.close()
-class CodeModeClient:
- """A leased GUI/idalib database accessed through ``ida_codemode``."""
+class NexusClient:
+ """A leased GUI/idalib database accessed through ``ida_nexus``."""
def __init__(
self,
@@ -298,23 +298,23 @@ class CodeModeClient:
self._last_instance: DatabaseInstance | None = None
self._connect_lock = threading.Lock()
- def connect(self, timeout: float = 1800.0, progress=None) -> "CodeModeClient":
- _require_codemode()
+ def connect(self, timeout: float = 1800.0, progress=None) -> "NexusClient":
+ _require_nexus()
with self._connect_lock:
handle = self._handle
if handle is not None:
if handle.connected:
return self
raise IDAConnectionError(
- "Code Mode database disconnected; explicit rediscovery required"
+ "IDA Nexus database disconnected; explicit rediscovery required"
)
if progress:
progress(
- f"discovering Code Mode database for {os.path.basename(self._path)}…"
+ f"discovering IDA Nexus database for {os.path.basename(self._path)}…"
)
try:
# A Ctrl+L reload releases its current managed-worker lease, but
- # that worker remains registered during Code Mode's final-lease
+ # that worker remains registered during IDA Nexus's final-lease
# grace period. Retry only that known handoff window. A GUI or
# another long-lived client remains busy and yields a clear
# failure rather than being modified underneath its owner.
@@ -329,7 +329,7 @@ class CodeModeClient:
output_database=self._output_database,
processor=self._processor,
# The natural byte address is converted to IDA's
- # paragraph-based -b value by Code Mode.
+ # paragraph-based -b value by IDA Nexus.
image_base=self._loading_address,
file_type=self._file_type,
new_database=self._new_database,
@@ -341,7 +341,7 @@ class CodeModeClient:
raise
if progress:
progress(
- "waiting for the previous Code Mode lease to close…"
+ "waiting for the previous IDA Nexus lease to close…"
)
owner = find_database_owner(
self._path,
@@ -389,15 +389,15 @@ class CodeModeClient:
return handle is not None and handle.owns_event(event)
def subscribe_idb_events(self):
- """Open Code Mode's closeable IDB-change iterator."""
+ """Open IDA Nexus's closeable IDB-change iterator."""
if not self.connected:
self.connect()
handle = self._handle
if handle is None:
- raise IDAConnectionError("Code Mode database is not connected")
+ raise IDAConnectionError("IDA Nexus database is not connected")
try:
return handle.subscribe_idb_events()
- except (DatabaseDisconnectedError, CodeModeConnectionError) as exc:
+ except (DatabaseDisconnectedError, NexusConnectionError) as exc:
raise self._connection_error(exc) from exc
def watch_idb_events(
@@ -425,7 +425,7 @@ class CodeModeClient:
self.connect()
handle = self._handle
if handle is None:
- raise IDAConnectionError("Code Mode database is not connected")
+ raise IDAConnectionError("IDA Nexus database is not connected")
try:
return remote(handle, **args)
except RemoteError as exc:
@@ -435,7 +435,7 @@ class CodeModeClient:
if exc.code == "operation_timeout":
raise IDATimeoutError(message) from exc
raise IDAToolError(name, message) from exc
- except (DatabaseDisconnectedError, CodeModeConnectionError) as exc:
+ except (DatabaseDisconnectedError, NexusConnectionError) as exc:
raise self._connection_error(exc) from exc
def save_database(self) -> dict[str, Any]:
@@ -443,12 +443,12 @@ class CodeModeClient:
self.connect()
handle = self._handle
if handle is None:
- raise IDAConnectionError("Code Mode database is not connected")
+ raise IDAConnectionError("IDA Nexus database is not connected")
try:
return handle.save_database()
except RemoteError as exc:
raise IDAToolError("save_database", str(exc)) from exc
- except (DatabaseDisconnectedError, CodeModeConnectionError) as exc:
+ except (DatabaseDisconnectedError, NexusConnectionError) as exc:
raise self._connection_error(exc) from exc
def discard_database(self, timeout: float = 5.0) -> bool:
@@ -477,7 +477,7 @@ class CodeModeClient:
time.sleep(0.05)
continue
raise IDAToolError("shutdown_database", str(exc)) from exc
- except (DatabaseDisconnectedError, CodeModeConnectionError) as exc:
+ except (DatabaseDisconnectedError, NexusConnectionError) as exc:
raise self._connection_error(exc) from exc
def health(self) -> dict[str, Any]:
@@ -544,7 +544,7 @@ class CodeModeClient:
return False
return wait_database_released(instance, timeout)
- def __enter__(self) -> "CodeModeClient":
+ def __enter__(self) -> "NexusClient":
return self.connect()
def __exit__(self, *exc) -> None:
diff --git a/idatui/pane.py b/idatui/pane.py
index c31a93c..1eee847 100644
--- a/idatui/pane.py
+++ b/idatui/pane.py
@@ -24,7 +24,7 @@ per pane in the registry, so stop/list/capture/keys keep working across both
python -m idatui.pane keys --pane <pane> Escape
Requires: running inside tmux or zellij. Each pane leases a registered GUI or
-shared managed idalib database through Code Mode. Uses ~/ida-venv/bin/python for
+shared managed idalib database through IDA Nexus. Uses ~/ida-venv/bin/python for
the TUI (needs textual) unless --python / IDATUI_PYTHON says otherwise.
"""
from __future__ import annotations
@@ -250,8 +250,8 @@ def _pane_keys(pane: str, keys: list[str], mux: str | None = None) -> None:
subprocess.run(["tmux", "send-keys", "-t", pane, *keys], check=True)
-# Code Mode owns database process lifetime: a closed pane drops its lease at the
-# socket/kernel boundary and Code Mode decides whether a managed worker still
+# IDA Nexus owns database process lifetime: a closed pane drops its lease at the
+# socket/kernel boundary and IDA Nexus decides whether a managed worker still
# has clients. There is nothing for the pane layer to reap.
@@ -261,7 +261,7 @@ def _count_live_panes() -> int:
def _reap_orphan_workers(force: bool = False) -> int:
- """Compatibility no-op: Code Mode workers are shared and lease-managed."""
+ """Compatibility no-op: IDA Nexus workers are shared and lease-managed."""
del force
return 0
@@ -294,7 +294,7 @@ def spawn(args) -> int:
print(f"error: no such project: {project}", file=sys.stderr)
return 2
- # The pane owns only the TUI. Code Mode's lease cleanup handles crashes;
+ # The pane owns only the TUI. IDA Nexus's lease cleanup handles crashes;
# kill-pane must never reap a shared GUI/idalib database.
if project is not None:
# launch takes: --project FILE [binaries...]; extra binaries are added to
@@ -347,7 +347,7 @@ def _wait_ready(sock: str, timeout: float, pane: str,
stuck_after: float = 45.0, mux: str | None = None) -> dict[str, Any]:
"""Poll the socket + ping until the TUI reports ready (or timeout).
- Emits a one-time hint if Code Mode discovery/opening is still not ready after
+ Emits a one-time hint if IDA Nexus discovery/opening is still not ready after
``stuck_after`` seconds.
"""
start = time.time()
@@ -370,7 +370,7 @@ def _wait_ready(sock: str, timeout: float, pane: str,
why = ("RPC socket not created yet" if not os.path.exists(sock)
else "TUI up but analysis not ready")
print(f"still waiting ({int(time.time() - start)}s): {why}. "
- f"Check Code Mode registrations and worker logs.", file=sys.stderr)
+ f"Check IDA Nexus registrations and worker logs.", file=sys.stderr)
time.sleep(0.4)
last = dict(last)
last["ready"] = False
@@ -467,7 +467,7 @@ def list_panes(args) -> int:
def reap(args) -> int:
- """Deprecated no-op; shared Code Mode workers are managed by leases."""
+ """Deprecated no-op; shared IDA Nexus workers are managed by leases."""
print(json.dumps({"reaped_workers": 0, "live_panes": _count_live_panes(),
"forced": args.force, "deprecated": True}))
return 0
@@ -572,7 +572,7 @@ def main(argv: list[str]) -> int:
ls.add_argument("--prune", action="store_true", help="drop dead panes (and their sockets)")
ls.set_defaults(fn=list_panes)
- rp = sub.add_parser("reap", help="deprecated no-op (Code Mode uses shared leases)")
+ rp = sub.add_parser("reap", help="deprecated no-op (IDA Nexus uses shared leases)")
rp.add_argument("--force", action="store_true", help=argparse.SUPPRESS)
rp.set_defaults(fn=reap)
diff --git a/idatui/pool.py b/idatui/pool.py
index 573aa75..2407b44 100644
--- a/idatui/pool.py
+++ b/idatui/pool.py
@@ -1,6 +1,6 @@
-"""DatabasePool — LRU leases on Code Mode databases for a project.
+"""DatabasePool — LRU leases on IDA Nexus databases for a project.
-Code Mode may bind a lease to an existing IDA GUI or to a shared managed idalib
+IDA Nexus may bind a lease to an existing IDA GUI or to a shared managed idalib
worker. The pool therefore owns *client interest*, never an IDA process. Releasing
an LRU entry persists managed IDBs but does not implicitly save a GUI, then closes
only this TUI's lease; other clients and GUI sessions remain alive. Managed workers exit themselves after their final lease.
@@ -48,8 +48,8 @@ def _pss_mb(pid: int | None) -> int:
def _default_spawn(ref: BinaryRef, ttl: int, *, new_database: bool = False): # pragma: no cover - needs IDA
- from .codemode_client import CodeModeClient
- return CodeModeClient(
+ from .nexus_client import NexusClient
+ return NexusClient(
ref.staged,
ttl=ttl,
load_args=ref.load_args,
@@ -59,7 +59,7 @@ def _default_spawn(ref: BinaryRef, ttl: int, *, new_database: bool = False): #
class DatabasePool:
- """Live Code Mode database leases, keyed by project label."""
+ """Live IDA Nexus database leases, keyed by project label."""
def __init__(self, project: Project, *, budget_mb: int | None = None,
ttl: int = 1800, spawn=None, mem_fn=None) -> None:
@@ -101,7 +101,7 @@ class DatabasePool:
"""A live client for ``label``, attaching or spawning as needed.
Do not sweep IDA scratch files here: a registered GUI or another Code
- Mode client may own the database. Code Mode's registry locks and health
+ Mode client may own the database. IDA Nexus's registry locks and health
probes are the authority for safe discovery and stale-record cleanup.
"""
client = self._clients.get(label)
@@ -269,5 +269,3 @@ class DatabasePool:
f"{self.memory_mb()}/{self.budget_mb}MB active={self.active}>")
-# Source compatibility for callers that imported the pre-Code-Mode name.
-WorkerPool = DatabasePool
diff --git a/idatui/project.py b/idatui/project.py
index 53f3b0a..e681dfc 100644
--- a/idatui/project.py
+++ b/idatui/project.py
@@ -23,7 +23,7 @@ firmware image, a cleaned build tree).
A source whose size/mtime no longer matches the staged copy is re-staged, and its
now-stale database is dropped (the DB describes the old bytes).
-The model has no IDA imports. Staging consults ida_codemode's registry before
+The model has no IDA imports. Staging consults ida_nexus's registry before
replacing files so it never mutates a database owned by a GUI/shared worker.
"""
from __future__ import annotations
@@ -57,7 +57,7 @@ class BinaryRef:
#: it is, a raw firmware image doesn't, and IDA defaults to metapc at 0.
processor: str = "" # IDA processor name: arm, armb, mipsb, metapc, …
base: int = 0 # load address (natural, e.g. 0x8000000)
- ida_args: str = "" # legacy -p/-b/-T switches accepted by Code Mode adapter
+ ida_args: str = "" # legacy -p/-b/-T switches accepted by IDA Nexus adapter
@property
def db(self) -> str:
@@ -311,7 +311,7 @@ class Project:
"""Ensure ``ref`` is staged in the sidecar; returns the staged path.
Re-staging a changed source drops its database: the DB describes the old
- bytes. Refuse while Code Mode reports a GUI/idalib owner; replacing a
+ bytes. Refuse while IDA Nexus reports a GUI/idalib owner; replacing a
staged executable or IDB underneath a shared live instance is corruption.
"""
if not os.path.isfile(ref.source):
@@ -319,15 +319,15 @@ class Project:
if not self.is_stale(ref):
return ref.staged
try:
- from .codemode_client import database_owner
+ from .nexus_client import database_owner
owner = database_owner(ref.db, ref.staged)
except Exception as exc:
raise ProjectError(
- f"cannot verify Code Mode ownership before staging {ref.label}: {exc}"
+ f"cannot verify IDA Nexus ownership before staging {ref.label}: {exc}"
) from exc
if owner is not None:
raise ProjectError(
- f"cannot restage {ref.label}: Code Mode instance {owner.record_id} "
+ f"cannot restage {ref.label}: IDA Nexus instance {owner.record_id} "
f"still owns {owner.idb_path}; close/release it first"
)
os.makedirs(self.bin_dir, exist_ok=True)
@@ -353,7 +353,7 @@ class Project:
def sweep_scratch(self, ref: BinaryRef) -> int:
"""Delete unpacked working files (never the ``.i64``) for maintenance.
- Runtime paths no longer call this: Code Mode instances are shared, so a
+ Runtime paths no longer call this: IDA Nexus instances are shared, so a
registry owner may still be using these files. Callers must independently
prove that no GUI/idalib instance owns the database.
"""
diff --git a/idatui/remote_ops.py b/idatui/remote_ops.py
index 91ffeb7..a67b2b7 100644
--- a/idatui/remote_ops.py
+++ b/idatui/remote_ops.py
@@ -1,4 +1,4 @@
-"""Typed remote operations executed through ida-codemode."""
+"""Typed remote operations executed through ida-nexus."""
from __future__ import annotations
# ruff: noqa
@@ -1655,7 +1655,7 @@ def _bindings() -> dict[Callable[..., Any], Any]:
with _BIND_LOCK:
if _BOUND is not None:
return _BOUND
- from ida_codemode import RemoteModule
+ from ida_nexus import RemoteModule
operations_module = RemoteModule(
Path(__file__), operation_label=operation_label, codec="json"
diff --git a/idatui/remote_tools.py b/idatui/remote_tools.py
index db04244..6681379 100644
--- a/idatui/remote_tools.py
+++ b/idatui/remote_tools.py
@@ -1,8 +1,8 @@
-"""The IDAPython ida-tui runs inside the Code Mode sandbox.
+"""The IDAPython ida-tui runs inside the IDA Nexus sandbox.
Two features have no ida-domain surface at all and are carried over VERBATIM
from the tools ida-tui was developed against (`server/patch_server.py`'s
-injected BODY, which the Code Mode port deletes):
+injected BODY, which the IDA Nexus port deletes):
* `heads` -- the continuous listing. ida-domain enumerates defined heads and
renders plain disassembly; the listing also needs coalesced undefined runs,
@@ -20,8 +20,8 @@ what you see). A re-implementation drifts from it silently.
This file is SOURCE SHIPPED AS TEXT to the database process; it is never
imported here, because the ida_* modules do not exist in the TUI's interpreter.
-`codemode_client` reads it and prepends it to the relevant snippets. Keep it
-self-contained: no relative imports, nothing beyond what Code Mode provides.
+`nexus_client` reads it and prepends it to the relevant snippets. Keep it
+self-contained: no relative imports, nothing beyond what IDA Nexus provides.
"""
# ruff: noqa
@@ -29,7 +29,7 @@ import re as _re
# IDAPython, imported ONCE at module scope.
#
-# This file is never imported by the client -- codemode_client reads it as
+# This file is never imported by the client -- nexus_client reads it as
# TEXT and installs it as a module inside the database process -- so the
# no-IDA house rule that keeps idatui importable without IDA does not apply
# here, and these need not be function-local.
@@ -1740,7 +1740,7 @@ def decompile(addr, include_addresses=True):
Faithful to the tool ida-tui was written against, and in particular to its
COST: the per-line address anchor comes from ONE ``get_line_item`` at column
- 0 per line. The Code Mode port asked for the full per-column line map (what
+ 0 per line. The IDA Nexus port asked for the full per-column line map (what
``decomp_map`` is for) purely to fill in that anchor, which is thousands of
``get_line_item``+``dstr()`` calls per function instead of one per line, and
made every pseudocode open cost the same as opening the split view.