Alt modifier dropped on named keys (Alt+Enter arrives as plain `enter`)
Summary
XTermParser._sequence_to_key_events only applies the alt modifier when the resolved key name is a single character (if len(name) == 1 and alt). For a Meta-prefixed named key — e.g. Alt/Option+Enter, which a terminal sends as ESC + CR (\x1b\r) — the resolved name is enter (length 5), so the alt is silently dropped. The event arrives as plain enter, indistinguishable from a real Enter.
alt+j works; alt+enter, alt+tab, alt+backspace, etc. do not.
Impact
Apps can't bind alt+enter — the common "insert newline vs. submit" split in chat/agent CLIs — on terminals that deliver Meta as an ESC prefix rather than via the Kitty keyboard protocol (e.g. xterm.js-based terminals like Termius, macOS "Option as Meta").
Textual version
8.2.8 (latest on PyPI). The same guard is present on main.
Reproduction
from textual._xterm_parser import XTermParser
p = XTermParser(False)
keys = [t.key for t in p.feed("\x1b\r") if hasattr(t, "key")]
keys += [t.key for t in p.feed("") if hasattr(t, "key")] # EOF flush
print(keys) # ['enter'] -- expected ['alt+enter']Location
src/textual/_xterm_parser.py, in _sequence_to_key_events:
if len(name) == 1 and alt: # <-- drops alt for named keys
if name.isupper():
name = f"shift+{name.lower()}"
name = f"alt+{name}"
yield events.Key(name, sequence)Proposed fix
Apply the alt prefix regardless of name length; keep the shift/upper handling scoped to single characters:
if alt:
if len(name) == 1 and name.isupper():
name = f"shift+{name.lower()}"
name = f"alt+{name}"
yield events.Key(name, sequence)With this, \x1b\r yields alt+enter, and alt+j / plain enter / plain j are unchanged.
Source: Textualize/textual