fix(rawcli): Ctrl-/ key binding crashed launch ("Invalid key: c-/")
prompt_toolkit doesn't accept "c-/". Ctrl-/ is emitted as Ctrl-_ (0x1F) by terminals, so bind "c-_" (plus "c-t" as an always-available fallback) for the hex/binary toggle. install_key_bindings() now ignores any key name a prompt_toolkit build rejects, so a bad key can never crash launch. Regression test added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -28,18 +28,32 @@ def byte_space(text: str, mode: str = HEX) -> str:
|
|||||||
return " ".join(compact[i:i + n] for i in range(0, len(compact), n))
|
return " ".join(compact[i:i + n] for i in range(0, len(compact), n))
|
||||||
|
|
||||||
|
|
||||||
def install_key_bindings(kb, session) -> None:
|
#: keys that toggle entry mode. Ctrl-/ is emitted as Ctrl-_ (0x1F) by most terminals; c-t is an
|
||||||
"""Wire Ctrl-/ (toggle entry mode) and live auto-byte-spacing of digit input onto ``kb``.
|
#: always-available fallback that power users can rely on regardless of terminal.
|
||||||
|
TOGGLE_KEYS = ("c-_", "c-t")
|
||||||
|
|
||||||
``session`` is the :class:`~pm3py.cli.rawcli.session.RawSession` whose ``entry_mode`` is
|
|
||||||
toggled and read."""
|
def _bind(kb, key, handler) -> None:
|
||||||
|
"""Add a binding, ignoring key names a given prompt_toolkit build doesn't accept."""
|
||||||
|
try:
|
||||||
|
kb.add(key)(handler)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def install_key_bindings(kb, session) -> None:
|
||||||
|
"""Wire the entry-mode toggle (Ctrl-/ , via Ctrl-_ , plus Ctrl-t) and live auto-byte-spacing
|
||||||
|
of digit input onto ``kb``. ``session`` is the :class:`RawSession` whose ``entry_mode`` is
|
||||||
|
toggled and read. Never raises on an unknown key name."""
|
||||||
from prompt_toolkit.document import Document
|
from prompt_toolkit.document import Document
|
||||||
|
|
||||||
@kb.add("c-/")
|
|
||||||
def _toggle(event):
|
def _toggle(event):
|
||||||
session.toggle_entry_mode()
|
session.toggle_entry_mode()
|
||||||
event.app.invalidate() # redraw the breadcrumb
|
event.app.invalidate() # redraw the breadcrumb
|
||||||
|
|
||||||
|
for key in TOGGLE_KEYS:
|
||||||
|
_bind(kb, key, _toggle)
|
||||||
|
|
||||||
def _regroup_after(event):
|
def _regroup_after(event):
|
||||||
buf = event.current_buffer
|
buf = event.current_buffer
|
||||||
buf.insert_text(event.data)
|
buf.insert_text(event.data)
|
||||||
@@ -49,4 +63,4 @@ def install_key_bindings(kb, session) -> None:
|
|||||||
buf.document = Document(grouped, len(grouped))
|
buf.document = Document(grouped, len(grouped))
|
||||||
|
|
||||||
for ch in sorted(_DIGITS[HEX] | _DIGITS[BIN]):
|
for ch in sorted(_DIGITS[HEX] | _DIGITS[BIN]):
|
||||||
kb.add(ch)(_regroup_after)
|
_bind(kb, ch, _regroup_after)
|
||||||
|
|||||||
@@ -326,3 +326,13 @@ class TestCompleter:
|
|||||||
s.protocol, s.transponder = "hf14a", "NTAG215"
|
s.protocol, s.transponder = "hf14a", "NTAG215"
|
||||||
cs = self._complete(s, "help GET")
|
cs = self._complete(s, "help GET")
|
||||||
assert any(c.text == "GET_VERSION" for c in cs)
|
assert any(c.text == "GET_VERSION" for c in cs)
|
||||||
|
|
||||||
|
|
||||||
|
class TestKeyBindings:
|
||||||
|
def test_install_does_not_raise(self):
|
||||||
|
# regression: "c-/" is not a valid prompt_toolkit key and used to crash launch
|
||||||
|
from prompt_toolkit.key_binding import KeyBindings
|
||||||
|
from pm3py.cli.rawcli.entry import install_key_bindings, TOGGLE_KEYS
|
||||||
|
kb = KeyBindings()
|
||||||
|
install_key_bindings(kb, RawSession())
|
||||||
|
assert len(kb.bindings) >= len(TOGGLE_KEYS)
|
||||||
|
|||||||
Reference in New Issue
Block a user