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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user