aboutsummaryrefslogtreecommitdiffstats
path: root/idatui
diff options
context:
space:
mode:
Diffstat (limited to 'idatui')
-rw-r--r--idatui/domain.py27
1 files changed, 16 insertions, 11 deletions
diff --git a/idatui/domain.py b/idatui/domain.py
index e107786..b5081cf 100644
--- a/idatui/domain.py
+++ b/idatui/domain.py
@@ -27,6 +27,7 @@ import re
import threading
import urllib.request
from concurrent.futures import ThreadPoolExecutor
+from collections.abc import Sequence
from dataclasses import dataclass, field, replace
from typing import NamedTuple
from typing import Callable, TYPE_CHECKING
@@ -115,12 +116,18 @@ class Head(NamedTuple):
#: [(kind, text)] from IDA's own colour tags — mnem/reg/num/name/str/punct/…
#: None when the worker didn't provide them (older worker, or the spans
#: disagreed with the plain text, in which case the text wins).
- spans: tuple[tuple[str, str], ...] | None = None
+ #:
+ #: Held exactly as it came off the wire, and **read-only**. The worker
+ #: memoises its per-line render, so one list is shared by every row that
+ #: says the same thing — pickle preserves that, and 228 000 rows of bash
+ #: reference about 53 000 lists. Copying each row's into a fresh tuple threw
+ #: the sharing away and cost 0.9 µs a row for nothing.
+ spans: Sequence | None = None
#: [(start, end, n)] — where each operand sits in ``text``, from IDA's own
#: COLOR_OPND markers. Lets the view show which operand the cursor is on,
#: and is the same information the worker maps a column through, so the
- #: highlight and the edit can't disagree.
- ops: tuple[tuple[int, int, int], ...] | None = None
+ #: highlight and the edit can't disagree. Read-only, as ``spans`` is.
+ ops: Sequence | None = None
@property
def label(self) -> str | None: # Line-compatible alias
@@ -135,12 +142,10 @@ class Head(NamedTuple):
@classmethod
def from_raw(cls, d: dict, raw: bytes | None = None) -> "Head":
- sp = d.get("spans")
- ops = d.get("ops")
- # ``tuple(map(tuple, ...))`` rather than a per-item genexpr with str()/
- # int() coercion: this runs once per listing row (hundreds of thousands
- # on a real binary) and the worker's own tool already emits [str, str]
- # and [int, int, int]. The coercion was re-proving that on every row.
+ # Spans and operand extents are stored as they arrive: the worker's own
+ # tool emits [str, str] and [int, int, int], so re-coercing them was
+ # re-proving that once per listing row -- and copying them into tuples
+ # destroyed the sharing the worker's line cache had just created.
return cls(
ea=_as_int(d["ea"]),
kind=d.get("kind", "unknown"),
@@ -148,8 +153,8 @@ class Head(NamedTuple):
text=d.get("text", ""),
name=d.get("name"),
raw=raw,
- spans=tuple(map(tuple, sp)) if sp else None,
- ops=tuple(map(tuple, ops)) if ops else None,
+ spans=d.get("spans") or None,
+ ops=d.get("ops") or None,
)