diff --git a/pyte/streams.py b/pyte/streams.py index cd0f51c..4269373 100644 --- a/pyte/streams.py +++ b/pyte/streams.py @@ -94,6 +94,7 @@ class Stream: } #: CSI escape sequences -- ``CSI P1;P2;...;Pn ``. + # Note that Pn can contain digits or `:` csi = { esc.ICH: "insert_characters", esc.CUU: "cursor_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 `_ @@ -318,8 +323,12 @@ def create_dispatcher(mapping: Mapping[str, str]) -> Dict[str, Callable[..., Non # `VT220 Programmer Ref. `_ # For details on the characters valid for use as # arguments. + # + # `XTerm `_ + # "Using semicolon was incorrect because [...]" + # params = [] - current = "" + param_with_subparameters = [0] private = False while True: char = yield None @@ -337,17 +346,24 @@ def create_dispatcher(mapping: Mapping[str, str]) -> Dict[str, Callable[..., Non draw(char) break elif char.isdigit(): - current += char + digit_value = ord(char) - ord("0") + 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(min(int(current or 0), 9999)) + # Note: pyte currently doesn't support subparameters. + # Ideally, we'd update SGR handling to be aware of it. + # That's tracked by . + current_param, *_subparameters = param_with_subparameters + params.append(current_param) if char == ";": - current = "" + param_with_subparameters = [0] else: if private: csi_dispatch[char](*params, private=True) diff --git a/tests/test_stream.py b/tests/test_stream.py index 7a3ad92..d46ed60 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -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():