Skip to content

Commit

Permalink
Rework CSI parsing to be aware of subparameters
Browse files Browse the repository at this point in the history
This fixes selectel#178.

Before, we assumed that CSI parameters are only integers. However, that
caused us to barf in weird ways when presented with CSI parameters that
contain `:`-delimited subparameters.

This update to the parsing code causes us to parse those subparameters,
and then immediately discard them. That may seem kind of weird, but I'm
laying the groundwork for a followup change to the SGR handling code to
actually be aware of subparameters
(selectel#179). I just didn't want to
muddy this diff with that change as well.
  • Loading branch information
jfly committed Oct 8, 2024
1 parent e59101f commit 80915c7
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 5 deletions.
25 changes: 20 additions & 5 deletions pyte/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class Stream:
}

#: CSI escape sequences -- ``CSI P1;P2;...;Pn <fn>``.
# Note that Pn can contain digits or `:`
csi = {
esc.ICH: "insert_characters",
esc.CUU: "cursor_up",
Expand Down Expand Up @@ -306,10 +307,14 @@ def create_dispatcher(mapping: Mapping[str, str]) -> Dict[str, Callable[..., Non
basic_dispatch[char]()
elif char == CSI_C1:
# All parameters are unsigned, positive decimal integers, with
# the most significant digit sent first. Any parameter greater
# the most significant digit sent first*. Any parameter greater
# than 9999 is set to 9999. If you do not specify a value, a 0
# value is assumed.
#
# *Not entirely true: Some SGR parameters allow `:`-delimited additional
# subparameters. These additional subparameters are a list of positive decimal integers
# following the above rules.
#
# .. seealso::
#
# `VT102 User Guide <http://vt100.net/docs/vt102-ug/>`_
Expand All @@ -318,8 +323,12 @@ def create_dispatcher(mapping: Mapping[str, str]) -> Dict[str, Callable[..., Non
# `VT220 Programmer Ref. <http://vt100.net/docs/vt220-rm/>`_
# For details on the characters valid for use as
# arguments.
#
# `XTerm <https://invisible-island.net/xterm/xterm.faq.html#color_by_number>`_
# "Using semicolon was incorrect because [...]"
#
params = []
current = 0
param_with_subparameters = [0]
private = False
while True:
char = yield None
Expand All @@ -338,17 +347,23 @@ def create_dispatcher(mapping: Mapping[str, str]) -> Dict[str, Callable[..., Non
break
elif char.isdigit():
digit_value = ord(char) - ord("0")
current = min(current * 10 + digit_value, 9999)
param_with_subparameters[-1] = min(param_with_subparameters[-1] * 10 + digit_value, 9999)
elif char == ":":
param_with_subparameters.append(0)
elif char == "$":
# XTerm-specific ESC]...$[a-z] sequences are not
# currently supported.
yield None
break
else:
params.append(current)
# Note: pyte currently doesn't support subparameters.
# Ideally, we'd update SGR handling to be aware of it.
# That's tracked by <https://github.com/selectel/pyte/issues/179>.
current_param, *_subparameters = param_with_subparameters
params.append(current_param)

if char == ";":
current = 0
param_with_subparameters = [0]
else:
if private:
csi_dispatch[char](*params, private=True)
Expand Down
13 changes: 13 additions & 0 deletions tests/test_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ def test_unknown_sequences():
assert handler.args == (6, 0)
assert handler.kwargs == {}

def test_csi_param_with_colon_context():
handler = argcheck()
screen = pyte.Screen(80, 24)
screen.debug = handler

stream = pyte.Stream(screen)
stream.feed(ctrl.CSI + "6:4Z")
assert handler.count == 1
# Note: currently pyte doesn't actually do anything with `:`-delimited context,
# which is why the `4` disappears here.
assert handler.args == ((6),)
assert handler.kwargs == {}


def test_non_csi_sequences():
for cmd, event in pyte.Stream.csi.items():
Expand Down

0 comments on commit 80915c7

Please sign in to comment.