Skip to content

Commit 37aa89c

Browse files
committed
cleanup FX and manual checklist
1 parent 4591051 commit 37aa89c

4 files changed

Lines changed: 86 additions & 60 deletions

File tree

docs/pages/advanced_topics/kitty_keyboard_protocol.rst

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,20 @@ Input
5757
``src/prompt_toolkit/input/kitty_keyboard.py`` owns the ``CSI u``
5858
decoder. Covered:
5959

60-
- Functional keys from the Kitty spec: :kbd:`enter`, :kbd:`tab`,
61-
:kbd:`escape`, :kbd:`backspace`, :kbd:`f1`–:kbd:`f12`. Mapped to the
62-
nearest existing ``Keys`` value with Shift / Ctrl / Ctrl-Shift
63-
promotion where an enum exists. Arrow keys and the navigation block
64-
(:kbd:`home` / :kbd:`end` / :kbd:`pageup` / :kbd:`pagedown` /
65-
:kbd:`insert` / :kbd:`delete`) are **not** handled here — under
66-
flag 1 the Kitty spec keeps them in their legacy
67-
``CSI <n> ~`` / ``CSI <letter>`` / ``SS3 <letter>`` encoding even
68-
when modified, so they continue to travel through
69-
``ANSI_SEQUENCES`` (which already has the full modifier matrix).
60+
- The four functional keys whose single-byte legacy encoding collides
61+
with a ``Ctrl+letter``: :kbd:`enter` (=``\r``=:kbd:`c-m`),
62+
:kbd:`tab` (=``\t``=:kbd:`c-i`), :kbd:`escape`
63+
(=``\x1b``=:kbd:`c-[`), :kbd:`backspace` (=``\x7f``/:kbd:`c-h`).
64+
These are the only keys flag 1 actually re-encodes as ``CSI u``.
65+
Mapped to the nearest existing ``Keys`` value with Shift / Ctrl /
66+
Ctrl-Shift promotion where an enum exists. Arrow keys, the
67+
navigation block (:kbd:`home` / :kbd:`end` / :kbd:`pageup` /
68+
:kbd:`pagedown` / :kbd:`insert` / :kbd:`delete`), and
69+
:kbd:`f1`–:kbd:`f12` are **not** handled here — under flag 1 the
70+
Kitty spec keeps them in their legacy ``CSI <n> ~`` /
71+
``CSI <letter>`` / ``SS3 <letter>`` encoding even when modified, so
72+
they continue to travel through ``ANSI_SEQUENCES`` (which already
73+
has the full modifier matrix).
7074
- Printable Unicode keys with Ctrl (mapped to ``Keys.ControlX``) and
7175
Ctrl+Shift digits (mapped to ``Keys.ControlShift1`` …).
7276
- Alt as a meta prefix: emitted as ``(Keys.Escape, base_key)`` to match

examples/prompts/kitty-key-checklist.py

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,17 @@
1212
The bottom toolbar lists every such gesture. Press one and its row
1313
turns green. On terminals without the protocol, rows stay grey —
1414
that's the expected fallback, not a bug. Press plain Enter to exit.
15+
16+
Pass ``--no-kitty`` to suppress the protocol push so you can verify
17+
that, without it, the gestures are indistinguishable from their legacy
18+
equivalents and the rows stay grey — useful for reproducing how the
19+
prompt behaves on a terminal that doesn't implement the protocol,
20+
from inside one that does.
1521
"""
1622

17-
from prompt_toolkit import prompt
23+
import argparse
24+
25+
from prompt_toolkit import PromptSession
1826
from prompt_toolkit.key_binding import KeyBindings
1927
from prompt_toolkit.styles import Style
2028

@@ -33,10 +41,28 @@
3341

3442

3543
def main():
44+
parser = argparse.ArgumentParser(description=__doc__.strip().splitlines()[0])
45+
parser.add_argument(
46+
"--no-kitty",
47+
action="store_true",
48+
help=(
49+
"Don't push the Kitty keyboard protocol. Modified keys then "
50+
"arrive as their legacy single-byte equivalents and the bindings "
51+
"in this example never fire — useful to verify the non-Kitty "
52+
"fallback from a Kitty-capable terminal."
53+
),
54+
)
55+
args = parser.parse_args()
56+
3657
pressed: set[str] = set()
3758

3859
def toolbar():
39-
lines = [("", "Kitty-only gestures — press each to turn it green:\n")]
60+
header = (
61+
"Kitty protocol DISABLED (--no-kitty) — rows stay grey:\n"
62+
if args.no_kitty
63+
else "Kitty-only gestures — press each to turn it green:\n"
64+
)
65+
lines = [("", header)]
4066
for binding, label in KITTY_KEYS:
4167
if binding in pressed:
4268
lines.append(("class:done", f" [x] {label}\n"))
@@ -70,14 +96,32 @@ def handler(event):
7096
}
7197
)
7298

73-
prompt(
99+
session = PromptSession(
74100
"> ",
75101
bottom_toolbar=toolbar,
76102
key_bindings=bindings,
77103
style=style,
78104
refresh_interval=0.5,
79105
)
80106

107+
if args.no_kitty:
108+
# Short-circuit the renderer's one-shot push/query block by
109+
# flipping its "already pushed" flag so the conditional at the
110+
# top of Renderer.render() skips both the push and the probe.
111+
# We hook `Application.on_reset` rather than setting the flag
112+
# directly — `Application.run()` calls `renderer.reset()`
113+
# first, which would clear a pre-set flag; `on_reset` fires
114+
# *after* that reset and before the first render, which is the
115+
# window we need. Reaching into the private attribute is fine
116+
# for a demo; there is no public "disable kitty" knob on
117+
# Application/Renderer today.
118+
def _suppress_kitty(_app):
119+
session.app.renderer._kitty_keyboard_pushed = True
120+
121+
session.app.on_reset += _suppress_kitty
122+
123+
session.prompt()
124+
81125

82126
if __name__ == "__main__":
83127
main()

src/prompt_toolkit/input/kitty_keyboard.py

Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -61,32 +61,27 @@
6161
_IGNORED_MODS = _CAPSLOCK | _NUMLOCK
6262

6363

64-
# Functional-key codes from the Kitty spec, mapped to the nearest
65-
# existing prompt_toolkit `Keys` value. Only keys likely to appear under
66-
# `disambiguate` (flag 1) are included.
64+
# Functional-key codes from the Kitty spec that can reach this decoder
65+
# under flag 1 ("disambiguate escape codes"), mapped to the nearest
66+
# existing prompt_toolkit `Keys` value. Everything else listed in the
67+
# spec's functional-key table keeps its legacy encoding under flag 1
68+
# and travels through `ANSI_SEQUENCES` instead:
69+
#
70+
# - Arrow keys and the navigation block (Home / End / PageUp /
71+
# PageDown / Insert / Delete): legacy `CSI <letter>` / `CSI <n>~`.
72+
# - F1-F12: legacy `SS3 <letter>` (F1-F4) / `CSI <n>~` (F5-F12).
73+
# - CapsLock / ScrollLock / NumLock / PrintScreen / Pause / Menu: no
74+
# legacy encoding and no matching `Keys` enum, so out of scope here.
75+
#
76+
# What remains are the four keys whose single-byte legacy encoding
77+
# collides with a Ctrl+letter (Enter=\r=C-m, Tab=\t=C-i, Esc=\x1b=C-[,
78+
# Backspace=\x7f/C-h) — those are the only gestures flag 1 actually
79+
# re-encodes as `CSI u`.
6780
_FUNCTIONAL: dict[int, Keys] = {
6881
13: Keys.ControlM, # Enter
6982
9: Keys.ControlI, # Tab
7083
27: Keys.Escape,
7184
127: Keys.ControlH, # Backspace
72-
# 57358 = CapsLock,
73-
# 57359 = ScrollLock,
74-
# 57360 = NumLock,
75-
# 57361 = PrintScreen,
76-
# 57362 = Pause,
77-
# 57363 = Menu — no corresponding `Keys` enum, so left out of the table.
78-
57364: Keys.F1,
79-
57365: Keys.F2,
80-
57366: Keys.F3,
81-
57367: Keys.F4,
82-
57368: Keys.F5,
83-
57369: Keys.F6,
84-
57370: Keys.F7,
85-
57371: Keys.F8,
86-
57372: Keys.F9,
87-
57373: Keys.F10,
88-
57374: Keys.F11,
89-
57375: Keys.F12,
9085
}
9186

9287

@@ -214,16 +209,11 @@ def _apply_modifiers(base: Keys, ctrl: bool, shift: bool) -> Keys:
214209
Promote a plain functional `Keys` value to its richer Ctrl / Shift /
215210
Ctrl-Shift variant when prompt_toolkit has an enum for it.
216211
217-
`base` is one of the unmodified functional keys from `_FUNCTIONAL`
218-
(Enter, Tab, Escape, arrows, navigation block, F1–F12). `ctrl` and
219-
`shift` are booleans decoded from the modifier mask; the Alt bit is
220-
handled one level up in `decode_csi_u`, so it is intentionally not a
212+
`base` is one of the four unmodified functional keys from
213+
`_FUNCTIONAL` — Enter, Tab, Escape, Backspace. `ctrl` and `shift`
214+
are booleans decoded from the modifier mask; the Alt bit is handled
215+
one level up in `decode_csi_u`, so it is intentionally not a
221216
parameter here.
222-
223-
When no matching richer enum exists (e.g. plain Shift-Enter,
224-
Shift-Fn, Shift-Backspace), the base key is returned unchanged. The
225-
caller should treat those as "modifier silently folded away" rather
226-
than "decode failure".
227217
"""
228218
if base is Keys.ControlM: # Enter
229219
if ctrl and shift:
@@ -261,14 +251,4 @@ def _apply_modifiers(base: Keys, ctrl: bool, shift: bool) -> Keys:
261251
return Keys.ShiftBackspace
262252
return Keys.ControlH
263253

264-
# F1..F24 — Ctrl+ is a distinct enum; Shift+ is mapped to FN+12 in
265-
# prompt_toolkit's existing convention (F1 → F13 etc.), but we don't
266-
# emulate that here; Shift+Fn just returns Fn for now.
267-
if base.value.startswith("f") and base.value[1:].isdigit():
268-
if ctrl:
269-
ctrl_key: Keys | None = getattr(Keys, "Control" + base.name, None)
270-
if ctrl_key is not None:
271-
return ctrl_key
272-
return base
273-
274254
return base

tests/test_kitty_keyboard.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,24 +42,22 @@
4242
("\x1b[127;5u", Keys.ControlBackspace),
4343
("\x1b[127;6u", Keys.ControlShiftBackspace),
4444
# NOTE: arrow keys, navigation block (Insert / Delete / Home /
45-
# End / PageUp / PageDown), and F1–F4 intentionally do *not*
45+
# End / PageUp / PageDown), and F1–F12 intentionally do *not*
4646
# appear here. Under flag 1 (disambiguate) — the only flag
4747
# prompt_toolkit pushes — the Kitty spec keeps these keys in
4848
# their legacy `CSI <n> ~` / `CSI <letter>` / `SS3 <letter>`
4949
# encoding even when modified. They travel through
5050
# `ANSI_SEQUENCES` (see `test_parser_routes_legacy_modified_arrow`
51-
# below), not through the CSI u decoder. Codepoints 57348–57363
52-
# are only ever emitted under flag 8 (report-all-as-escape);
53-
# adding them back belongs with any flag-8 work, not here.
51+
# below), not through the CSI u decoder. Their functional
52+
# codepoints (57345–57375) would only appear under flag 8
53+
# (report-all-as-escape); adding handling for them belongs
54+
# with any flag-8 work, not here.
5455
# Ctrl + letter.
5556
("\x1b[97;5u", Keys.ControlA),
5657
("\x1b[111;5u", Keys.ControlO),
5758
("\x1b[122;5u", Keys.ControlZ),
5859
# Ctrl + digit.
5960
("\x1b[49;5u", Keys.Control1),
60-
# F-keys.
61-
("\x1b[57364u", Keys.F1),
62-
("\x1b[57364;5u", Keys.ControlF1),
6361
# Ctrl + Shift + digit.
6462
("\x1b[49;6u", Keys.ControlShift1),
6563
],

0 commit comments

Comments
 (0)