Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions src/textual/_xterm_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,20 +163,29 @@ def on_key_token(event: events.Key) -> None:
else:
on_token(event)

def reissue_sequence_as_keys(reissue_sequence: str) -> None:
def reissue_sequence_as_keys(
reissue_sequence: str, process_alt: bool = False
) -> None:
"""Called when an escape sequence hasn't been understood.

Args:
reissue_sequence: Key sequence to report to the app.
"""

alt = False

if reissue_sequence:
self.debug_log("REISSUE", repr(reissue_sequence))
for character in reissue_sequence:
key_events = sequence_to_key_events(character)
if process_alt and character == ESC:
alt = True
continue
key_events = sequence_to_key_events(character, alt=alt)
for event in key_events:
if event.key == "escape":
if event.key == "escape" and not process_alt:
event = events.Key("circumflex_accent", "^")
on_token(event)
alt = False

while not self.is_eof:
if not bracketed_paste and paste_buffer:
Expand Down Expand Up @@ -211,23 +220,25 @@ def reissue_sequence_as_keys(reissue_sequence: str) -> None:
# # Could be the escape key was pressed OR the start of an escape sequence
sequence: str = ESC

def send_escape() -> None:
def send_sequence(process_alt: bool = True) -> None:
"""Send escape key and reissue sequence."""
on_token(events.Key("escape", "\x1b"))
reissue_sequence_as_keys(sequence[1:])
if sequence == ESC:
on_token(events.Key("escape", "\x1b"))
else:
reissue_sequence_as_keys(sequence, process_alt=process_alt)

while True:
try:
new_character = yield read1(constants.ESCAPE_DELAY)
except ParseTimeout:
send_escape()
send_sequence()
break
except ParseEOF:
send_escape()
send_sequence()
return

if new_character == ESC:
send_escape()
send_sequence(process_alt=False)
sequence = character
continue
else:
Expand Down Expand Up @@ -313,7 +324,9 @@ def send_escape() -> None:
self._debug_log_file.close()
self._debug_log_file = None

def _sequence_to_key_events(self, sequence: str) -> Iterable[events.Key]:
def _sequence_to_key_events(
self, sequence: str, alt: bool = False
) -> Iterable[events.Key]:
"""Map a sequence of code points on to a sequence of keys.

Args:
Expand Down Expand Up @@ -342,7 +355,7 @@ def _sequence_to_key_events(self, sequence: str) -> Iterable[events.Key]:
key_tokens.append(modifier)

key_tokens.sort()
key_tokens.append(key)
key_tokens.append(key.lower())
yield events.Key(
"+".join(key_tokens), sequence if len(sequence) == 1 else None
)
Expand Down Expand Up @@ -376,7 +389,12 @@ def _sequence_to_key_events(self, sequence: str) -> Iterable[events.Key]:
name = _character_to_key(sequence)
else:
name = sequence

name = KEY_NAME_REPLACEMENTS.get(name, name)
if len(name) == 1 and alt:
if name.isupper():
name = f"shift+{name.lower()}"
name = f"alt+{name}"
yield events.Key(name, sequence)
except Exception:
yield events.Key(sequence, sequence)
29 changes: 26 additions & 3 deletions tests/test_xterm_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,13 @@ def test_unknown_sequence_followed_by_known_sequence(parser, chunk_size):
events = []

for chunk in chunks(sequence, chunk_size):
events.append(parser.feed(chunk))
events.extend(list(parser.feed(chunk)))

events = list(itertools.chain.from_iterable(list(event) for event in events))
# events = list(itertools.chain.from_iterable(list(event) for event in events))
print(repr([event.key for event in events]))

assert [event.key for event in events] == [
"escape",
"circumflex_accent",
"left_square_bracket",
"question_mark",
"end",
Expand Down Expand Up @@ -181,6 +181,29 @@ def test_double_escape(parser):
assert [event.key for event in events] == ["escape", "escape"]


@pytest.mark.parametrize(
"sequence,key",
[
("a", "a"),
("B", "B"),
("\x1ba", "alt+a"),
("\x1b[97;3u", "alt+a"),
("\x1b[65;4u", "alt+shift+a"),
("\x1bA", "alt+shift+a"),
("\x1b[120;7u", "alt+ctrl+x"),
],
)
def test_keys(parser, sequence: str, key: str) -> None:
"""Test rarer keys."""
events = []
for event in parser.feed(sequence):
events.append(event)
for event in parser.feed(""):
events.append(event)
event = events[0]
assert event.key == key


@pytest.mark.parametrize(
"sequence, event_type, shift, meta",
[
Expand Down
Loading