fix(rawcli): raw byte-line autocomplete fires in hex and binary
The digit key handler applied each keystroke as `buf.document = Document(...)`. Assigning the buffer document resets prompt_toolkit's complete_state and never re-fires completion, so the live autocomplete menu never appeared during raw byte entry -- in BOTH hex and binary (only command-name completion worked, since that path already used insert_text). type_digit only ever appends the digit (optionally after an auto-space) or drops it, so the change is always a pure suffix. Apply it with buf.insert_text, which triggers completion exactly like an ordinary keystroke. Verified by driving the real key binding: the completion menu now populates on every digit in hex and binary (before: complete_state None, start_completion never called). Adds a regression test asserting the handler routes through insert_text; updates the rawcli roadmap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11,17 +11,20 @@ dropdown** — never on the bottom bar, never in command output.
|
|||||||
- **Must work in both hex AND binary entry modes**, rendering in the active base.
|
- **Must work in both hex AND binary entry modes**, rendering in the active base.
|
||||||
- Function-call args are base-10 (`READ(4)`); raw bytes are hex, `0b` to intermix binary.
|
- Function-call args are base-10 (`READ(4)`); raw bytes are hex, `0b` to intermix binary.
|
||||||
|
|
||||||
## BUG — binary-mode autocomplete does not fire in the live prompt
|
## FIXED — raw byte-line autocomplete now fires in hex AND binary
|
||||||
|
|
||||||
The completer logic is correct when fed the text directly (hex and binary), but in the running
|
Root cause: `entry._digit` applied each keystroke as `buf.document = Document(...)`, which resets
|
||||||
REPL the completion menu doesn't reliably appear while entering **binary**.
|
prompt_toolkit's `complete_state` and never re-fires completion — so the live menu never appeared
|
||||||
- Prime suspect: `entry._regroup_after` sets `buf.document = Document(...)` directly on
|
during raw byte entry. This hit **both** hex and binary (the earlier "hex works, binary doesn't"
|
||||||
auto-spacing, which resets prompt_toolkit's `complete_state` and cancels the menu. Binary
|
was inaccurate; only command-name completion worked, because that path already used `insert_text`).
|
||||||
regroups every 8 digits, so it bites almost immediately.
|
Fixed by appending the per-keystroke delta via `buf.insert_text` instead — `type_digit` only ever
|
||||||
- Fix direction: regroup via a completion-preserving path (don't replace `buf.document`
|
appends (optionally after an auto-space) or drops, so the change is always a pure suffix, and
|
||||||
wholesale); re-arm `complete_while_typing` after regroup. Also confirm the Ctrl-t / Ctrl-_
|
`insert_text` triggers completion exactly like an ordinary keystroke. Verified by driving the real
|
||||||
toggle actually flips the breadcrumb to `0b`.
|
key handler: the menu now populates on every digit in both modes. Regression:
|
||||||
- Verify on hardware in binary mode (needs a tag on the antenna).
|
`TestKeyBindings::test_digit_binding_fires_completion_trigger_for_raw_bytes`.
|
||||||
|
|
||||||
|
Still worth a quick live-REPL eyeball with a tag on the antenna, and confirming the Ctrl-t / Ctrl-_
|
||||||
|
toggle flips the breadcrumb to `0b` (separate from the completion trigger, not yet re-verified).
|
||||||
|
|
||||||
## Priority 1 — MIFARE Classic (do this first)
|
## Priority 1 — MIFARE Classic (do this first)
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ def install_key_bindings(kb, session) -> None:
|
|||||||
"""Wire the entry-mode toggle (Ctrl-/ via Ctrl-_, plus Ctrl-t) and per-byte base-aware digit
|
"""Wire the entry-mode toggle (Ctrl-/ via Ctrl-_, plus Ctrl-t) and per-byte base-aware digit
|
||||||
entry onto ``kb``. ``session`` is the :class:`RawSession` whose ``entry_mode`` is toggled/read
|
entry onto ``kb``. ``session`` is the :class:`RawSession` whose ``entry_mode`` is toggled/read
|
||||||
and whose ``byte_bases`` records each raw byte's base. Never raises on an unknown key name."""
|
and whose ``byte_bases`` records each raw byte's base. Never raises on an unknown key name."""
|
||||||
from prompt_toolkit.document import Document
|
|
||||||
|
|
||||||
def _toggle(event):
|
def _toggle(event):
|
||||||
session.toggle_entry_mode() # only flips the mode — the next byte you type uses it
|
session.toggle_entry_mode() # only flips the mode — the next byte you type uses it
|
||||||
@@ -93,7 +92,13 @@ def install_key_bindings(kb, session) -> None:
|
|||||||
buf.insert_text(d)
|
buf.insert_text(d)
|
||||||
return
|
return
|
||||||
new_text, session.byte_bases = type_digit(text, session.byte_bases, session.entry_mode, d)
|
new_text, session.byte_bases = type_digit(text, session.byte_bases, session.entry_mode, d)
|
||||||
buf.document = Document(new_text, len(new_text))
|
# Apply the change as an append via insert_text, NOT `buf.document = …`: assigning the
|
||||||
|
# document resets prompt_toolkit's complete_state and never re-fires completion, which
|
||||||
|
# killed the live autocomplete menu during raw byte entry. type_digit only ever appends
|
||||||
|
# (optionally after an auto-space) or drops, so the delta is always a pure suffix.
|
||||||
|
suffix = new_text[len(text):]
|
||||||
|
if suffix:
|
||||||
|
buf.insert_text(suffix)
|
||||||
|
|
||||||
for ch in sorted(_DIGITS[HEX] | _DIGITS[BIN]):
|
for ch in sorted(_DIGITS[HEX] | _DIGITS[BIN]):
|
||||||
_bind(kb, ch, _digit)
|
_bind(kb, ch, _digit)
|
||||||
|
|||||||
@@ -908,3 +908,26 @@ class TestKeyBindings:
|
|||||||
for ch in "00000100":
|
for ch in "00000100":
|
||||||
press(ch)
|
press(ch)
|
||||||
assert buf.text == "30 00000100" and s.byte_bases == ["hex", "bin"]
|
assert buf.text == "30 00000100" and s.byte_bases == ["hex", "bin"]
|
||||||
|
|
||||||
|
def test_digit_binding_fires_completion_trigger_for_raw_bytes(self):
|
||||||
|
# regression: raw byte entry must append via insert_text (which fires completion), not
|
||||||
|
# replace buf.document (which resets complete_state and silently killed the live
|
||||||
|
# autocomplete menu during hex/binary byte entry). on_text_insert fires from insert_text,
|
||||||
|
# never from a document= swap, so it is a faithful proxy for "the menu would refresh".
|
||||||
|
from types import SimpleNamespace
|
||||||
|
from prompt_toolkit.key_binding import KeyBindings
|
||||||
|
from prompt_toolkit.buffer import Buffer
|
||||||
|
from pm3py.cli.rawcli.entry import install_key_bindings
|
||||||
|
s = RawSession()
|
||||||
|
s.toggle_entry_mode() # binary: regroups every 8 digits (worst case)
|
||||||
|
kb = KeyBindings()
|
||||||
|
install_key_bindings(kb, s)
|
||||||
|
digit = {getattr(b.keys[0], "value", b.keys[0]): b for b in kb.bindings}
|
||||||
|
buf = Buffer()
|
||||||
|
fired = []
|
||||||
|
buf.on_text_insert += lambda _b: fired.append(1)
|
||||||
|
app = SimpleNamespace(invalidate=lambda: None)
|
||||||
|
for ch in "00000100":
|
||||||
|
digit[ch].handler(SimpleNamespace(current_buffer=buf, app=app, data=ch))
|
||||||
|
assert buf.text == "00000100"
|
||||||
|
assert len(fired) == 8 # one insert_text per digit (0 with document=)
|
||||||
|
|||||||
Reference in New Issue
Block a user