fix(rawcli): all hints live in the completion dropdown, not the bottom bar

The memory-location info was being surfaced on the bottom toolbar (input_hint). It belongs in
the autocomplete dropdown — the same popup where identify/READ/… appear. Moved it there and
took it off the bottom bar entirely.

- Bottom toolbar shows only the entry-mode status now; the input_hint machinery is removed.
- Raw hex/binary entry: a matched opcode inserts the RAW BYTE (30, 00110000), labelled with
  the command name — not "READ(". You're building a raw byte string, so accepting a hint keeps
  you in raw bytes.
- New: after a page-command opcode in raw entry, the next byte gets the tag's memory map as raw
  bytes (30 04 -> "04  user memory"), matching what READ( offers. Renders in the entry base.
- Function-call page args still insert 0x-hex addresses (the chosen display).

Verified through the real interactive TUI (pty): typing "30 " pops the memory map in the
completion menu as raw bytes; the old bottom-bar hint string no longer appears anywhere. 1328
green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
michael
2026-07-15 19:09:16 -07:00
parent a3cef180d2
commit 65be53da8c
4 changed files with 87 additions and 123 deletions

View File

@@ -534,18 +534,39 @@ class TestCompleter:
cs = self._complete(s, "help GET")
assert any(c.text == "GET_VERSION" for c in cs)
def test_opcode_completion_hex(self):
def _disp(self, comp):
return comp.display[0][1] if comp.display else comp.text
def test_opcode_completion_hex_inserts_raw_byte(self):
# entering raw hex, a matched opcode inserts the RAW BYTE (60), labelled with the command
s = RawSession()
s.protocol, s.transponder = "hf14a", "NTAG213"
cs = self._complete(s, "60") # GET_VERSION opcode
assert any(c.text == "GET_VERSION(" for c in cs)
gv = next(c for c in cs if "GET_VERSION" in self._disp(c))
assert gv.text == "60" # not "GET_VERSION("
# a partial digit completes to the full opcode byte
assert "30" in [c.text for c in self._complete(s, "3")] # READ (0x30)
def test_opcode_completion_binary(self):
def test_opcode_completion_binary_inserts_raw_byte(self):
s = RawSession()
s.protocol, s.transponder = "hf14a", "NTAG213"
s.entry_mode = "bin"
cs = self._complete(s, "01100000") # 0x60 in binary
assert any(c.text == "GET_VERSION(" for c in cs)
gv = next(c for c in cs if "GET_VERSION" in self._disp(c))
assert gv.text == "01100000" # raw binary byte, not the command name
def test_raw_page_byte_completion(self):
# after a page-command opcode in raw entry, the page byte gets the memory map (raw bytes)
s = RawSession()
s.protocol, s.transponder = "hf14a", "NTAG213"
by_text = {c.text: c.display_meta_text for c in self._complete(s, "30 ")}
assert by_text["04"] == "user memory" # inserts raw byte 04, not 0x04
assert by_text["28"] == "dynamic lock bytes"
assert "PWD" in by_text["2B"]
# narrows as you type the byte
assert [c.text for c in self._complete(s, "30 2")] == ["28", "29", "2A", "2B", "2C"]
# a non-page opcode (GET_VERSION) gets no page menu
assert self._complete(s, "60 ") == []
def test_page_arg_lists_memory_map(self):
# typing the page argument suggests the tag's memory landmarks with their roles
@@ -621,32 +642,6 @@ class TestMemoryHint:
assert page_role("NTAG216", 0x04) == "user memory" # different IC, different pages
assert "AUTH0" in page_role("NTAG216", 0xE3)
def test_hint_command_and_page_location(self):
from pm3py.cli.rawcli.memory import input_hint
s = self._sess()
assert "user memory" in input_hint(s, "READ(4)")
assert "PWD" in input_hint(s, "READ(0x2B") # partial input + hex arg
assert "Capability Container" in input_hint(s, "WRITE(3,")
def test_hint_command_only(self):
from pm3py.cli.rawcli.memory import input_hint
s = self._sess()
assert input_hint(s, "GET_VERSION").startswith("GET_VERSION()")
assert input_hint(s, "") is None
def test_hint_needs_transponder(self):
from pm3py.cli.rawcli.memory import input_hint
assert input_hint(RawSession(), "READ(4)") is None
def test_hint_raw_bytes(self):
# raw hex (30 04) gets the location hint too — the opcode maps to READ, byte 1 is the page
from pm3py.cli.rawcli.memory import input_hint
s = self._sess() # NTAG213
h = input_hint(s, "30 04")
assert h.startswith("READ(page)") and "page 0x04 → user memory" in h
assert "PWD" in input_hint(s, "30 2B") # page 0x2B = PWD on a 213
assert input_hint(s, "60").startswith("GET_VERSION") # opcode-only command, no page part
def test_generic_type2_fallback(self):
from pm3py.cli.rawcli.memory import landmark_pages, page_role
# only the family is known (flaky/absent GET_VERSION) -> universal pages still resolve
@@ -659,15 +654,11 @@ class TestMemoryHint:
assert page_role("MIFARE Classic 1K", 4) is None # not Type 2 -> nothing
def test_t5577_block_roles(self):
from pm3py.cli.rawcli.memory import page_role, input_hint
from pm3py.cli.rawcli.memory import page_role
tp = "T5577 (EM4100 20260716FF)"
assert "config" in page_role(tp, 0)
assert "password" in page_role(tp, 7)
assert "data" in page_role(tp, 3)
s = RawSession()
s.protocol, s.transponder = "lf", tp
assert "config" in input_hint(s, "READ(0") # block-role relevance for LF too
assert "password" in input_hint(s, "WRITE(7,")
def test_layouts_match_models(self):
from pm3py.cli.rawcli.memory import _LAYOUTS