feat(rawcli): render page-argument completion in the active entry mode

The memory-map completion for a page/block argument now follows the entry mode instead of
always emitting hex:

  hex mode     READ(  -> 0x04        user memory
  binary mode  READ(  -> 0b00000100  user memory   (full 8 bits — each bit visible)

Matching narrows in the same base (a binary partial filters bit-prefix-wise), and a 0x/0b
prefix on the argument overrides the session mode. Binary matters here because for the config
pages each bit carries its own meaning (CFG0 AUTH0, CFG1 PROT/CFGLCK/AUTHLIM), so seeing the
whole pattern is the point.

Hardware-verified against a real NTAG216 in binary mode (0b00000000..0b11100110 with roles).
Tests for binary rendering, bit-prefix filtering, and prefix-override. 1271 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
michael
2026-07-15 16:29:37 -07:00
parent b6fdb7bca4
commit c53c087e84
2 changed files with 41 additions and 6 deletions

View File

@@ -65,23 +65,39 @@ class RawCompleter(Completer):
yield from self._catalog_names(word)
def _page_arg(self, cmd_name: str, partial: str):
"""Complete a page/block argument with the identified tag's memory landmarks, so
``READ(`` lists ``0x00 → UID``, ``0x03 → CC``, ``0xE3 → CFG0`` … as suggestions."""
"""Complete a page/block argument with the identified tag's memory landmarks, rendered in
the active entry mode — ``0x04`` in hex, ``0b00000100`` in binary (every bit visible,
since for config pages each bit carries its own meaning). ``READ(`` lists the whole map;
typing narrows it. A ``0x``/``0b`` prefix on the argument overrides the session mode."""
catalog = catalog_for(self._session)
if catalog is None:
return
tc = catalog.get(cmd_name)
if tc is None or not tc.params or tc.params[0] not in ("page", "block"):
return
low = partial.lower()
mode = self._arg_mode(partial)
body = partial.lower()
if body.startswith(("0x", "0b")):
body = body[2:]
for page, role in landmark_pages(self._session.transponder):
val = f"0x{page:02X}"
forms = (val.lower(), f"{page:x}", f"{page:02x}", str(page)) # 0x-, bare-hex, decimal
if low and not any(f.startswith(low) for f in forms):
if mode == "bin":
val, forms = f"0b{page:08b}", (f"{page:08b}", f"{page:b}")
else:
val, forms = f"0x{page:02X}", (f"{page:02x}", f"{page:x}", str(page))
if body and not any(f.startswith(body) for f in forms):
continue
yield Completion(val, start_position=-len(partial),
display=f"{val} {role}", display_meta=role)
def _arg_mode(self, partial: str) -> str:
"""hex/bin for an argument token — a 0x/0b prefix wins, else the session entry mode."""
low = partial.lower()
if low.startswith("0x"):
return "hex"
if low.startswith("0b"):
return "bin"
return "bin" if getattr(self._session, "entry_mode", "hex") == "bin" else "hex"
def _first_token(self, word: str):
low = word.lower()
for verb in sorted(CONTROL_VERBS):