From fcf57de49f9b992c444c97cf1825de1661c8d219 Mon Sep 17 00:00:00 2001 From: Josh Karpel Date: Sun, 31 Dec 2023 18:25:39 -0600 Subject: [PATCH] Parse VT inputs as bytes instead of strings (#83) --- counterweight/_utils.py | 6 +- counterweight/app.py | 8 +- counterweight/cli.py | 10 +- counterweight/events.py | 19 +- counterweight/input.py | 16 +- counterweight/keys.py | 326 +++++++++++++----------------- counterweight/output.py | 18 +- docs/input-handling/events.md | 31 +++ docs/reference/changelog.md | 13 +- mkdocs.yml | 1 + poetry.lock | 146 ++++++------- pyproject.toml | 2 +- tests/test_vt_parsing.py | 119 +++++++++++ tests/utils/test_partition_int.py | 2 +- 14 files changed, 408 insertions(+), 309 deletions(-) create mode 100644 tests/test_vt_parsing.py diff --git a/counterweight/_utils.py b/counterweight/_utils.py index ed9d3470..2acaefcc 100644 --- a/counterweight/_utils.py +++ b/counterweight/_utils.py @@ -52,14 +52,14 @@ def partition_int(total: int, weights: tuple[int]) -> list[int]: """Partition an integer into a list of integers, with each integer in the list corresponding to the weight at the same index in the weights list.""" # https://stackoverflow.com/questions/62914824/c-sharp-split-integer-in-parts-given-part-weights-algorithm + if any(w < 0 for w in weights): + raise ValueError("Weights must be non-negative") + if total == 0: # optimization return [0] * len(weights) total_weight = sum(weights) - if not total_weight > 0: - raise ValueError("Total weight must be positive") - partition = [] accumulated_diff = 0.0 for w in weights: diff --git a/counterweight/app.py b/counterweight/app.py index b627c663..1008236b 100644 --- a/counterweight/app.py +++ b/counterweight/app.py @@ -38,9 +38,9 @@ from counterweight.output import ( CLEAR_SCREEN, paint_to_instructions, - start_mouse_reporting, + start_mouse_tracking, start_output_control, - stop_mouse_reporting, + stop_mouse_tracking, stop_output_control, ) from counterweight.paint import Paint, paint_layout, svg @@ -128,7 +128,7 @@ def put_event(event: AnyEvent) -> None: if not headless: start_handling_resize_signal(put_event=put_event) start_output_control(stream=output_stream) - start_mouse_reporting(stream=output_stream) + start_mouse_tracking(stream=output_stream) key_thread = Thread(target=read_keys, args=(input_stream, put_event), daemon=True) key_thread.start() @@ -361,7 +361,7 @@ def handle_control(control: AnyControl | None) -> None: logger.info("Application stopping...") if not headless: - stop_mouse_reporting(stream=output_stream) + stop_mouse_tracking(stream=output_stream) stop_output_control(stream=output_stream) stop_input_control(stream=input_stream, original=original) stop_handling_resize_signal() diff --git a/counterweight/cli.py b/counterweight/cli.py index 6efe34bb..6e06302b 100644 --- a/counterweight/cli.py +++ b/counterweight/cli.py @@ -12,7 +12,7 @@ from counterweight.events import AnyEvent from counterweight.input import read_keys, start_input_control, stop_input_control from counterweight.logging import tail_devlog -from counterweight.output import start_mouse_reporting, stop_mouse_reporting +from counterweight.output import start_mouse_tracking, stop_mouse_tracking cli = Typer( name=PACKAGE_NAME, @@ -66,7 +66,7 @@ def put_event(event: AnyEvent) -> None: original = start_input_control(stream=input_stream) if mouse: - start_mouse_reporting(stream=output_stream) + start_mouse_tracking(stream=output_stream) try: while True: print("Waiting for input...") @@ -77,4 +77,8 @@ def put_event(event: AnyEvent) -> None: finally: stop_input_control(stream=input_stream, original=original) if mouse: - stop_mouse_reporting(stream=output_stream) + stop_mouse_tracking(stream=output_stream) + + +if __name__ == "__main__": + cli() diff --git a/counterweight/events.py b/counterweight/events.py index e69e84f6..5cca9e9d 100644 --- a/counterweight/events.py +++ b/counterweight/events.py @@ -1,41 +1,48 @@ -from time import monotonic_ns +from dataclasses import dataclass from typing import Union -from pydantic import Field - from counterweight.geometry import Position -from counterweight.types import FrozenForbidExtras -class _Event(FrozenForbidExtras): - timestamp_ns: int = Field(default_factory=monotonic_ns) +@dataclass(frozen=True) +class _Event: + pass +@dataclass(frozen=True) class TerminalResized(_Event): pass +@dataclass(frozen=True) class KeyPressed(_Event): key: str +@dataclass(frozen=True) class MouseMoved(_Event): position: Position + button: int | None +@dataclass(frozen=True) class MouseDown(_Event): position: Position button: int +@dataclass(frozen=True) class MouseUp(_Event): position: Position + button: int +@dataclass(frozen=True) class StateSet(_Event): pass +@dataclass(frozen=True) class Dummy(_Event): pass diff --git a/counterweight/input.py b/counterweight/input.py index a23f95f1..775c251a 100644 --- a/counterweight/input.py +++ b/counterweight/input.py @@ -9,7 +9,6 @@ from time import perf_counter_ns from typing import TextIO -from parsy import ParseError from structlog import get_logger from counterweight.events import AnyEvent @@ -31,11 +30,9 @@ def read_keys(stream: TextIO, put_event: Callable[[AnyEvent], None]) -> None: # so we can dispense with the ceremony of actually using the results of the select. start_parsing = perf_counter_ns() - b = os.read(stream.fileno(), 1_000) - bytes = list(b) - buffer = b.decode("utf-8") + bytes = os.read(stream.fileno(), 1_000) try: - inputs = vt_inputs.parse(buffer) + inputs = vt_inputs.parse(bytes) for i in inputs: put_event(i) @@ -43,17 +40,14 @@ def read_keys(stream: TextIO, put_event: Callable[[AnyEvent], None]) -> None: logger.debug( "Parsed user input", inputs=inputs, - buffer=repr(buffer), bytes=bytes, - len_buffer=len(buffer), elapsed_ns=f"{perf_counter_ns() - start_parsing:_}", ) - except (ParseError, KeyError) as e: + except Exception as e: logger.error( "Failed to parse input", - error=str(e), - buffer=repr(buffer), - len_buffer=len(buffer), + error=repr(e), + bytes=bytes, elapsed_ns=f"{perf_counter_ns() - start_parsing:_}", ) diff --git a/counterweight/keys.py b/counterweight/keys.py index 549e2ccd..aa6351ad 100644 --- a/counterweight/keys.py +++ b/counterweight/keys.py @@ -4,7 +4,7 @@ from enum import Enum from string import printable -from parsy import Parser, any_char, char_from, decimal_digit, generate, string +from parsy import Parser, char_from, generate, match_item from structlog import get_logger from counterweight.events import AnyEvent, KeyPressed, MouseDown, MouseMoved, MouseUp @@ -20,9 +20,6 @@ class Key(str, Enum): Escape = "escape" # Also Control-[ ShiftEscape = "shift+escape" - Return = "return" - - ControlAt = "ctrl+@" # Also Control-Space. ControlA = "ctrl+a" ControlB = "ctrl+b" @@ -51,43 +48,13 @@ class Key(str, Enum): ControlY = "ctrl+y" ControlZ = "ctrl+z" - Control1 = "ctrl+1" - Control2 = "ctrl+2" - Control3 = "ctrl+3" - Control4 = "ctrl+4" - Control5 = "ctrl+5" - Control6 = "ctrl+6" - Control7 = "ctrl+7" - Control8 = "ctrl+8" - Control9 = "ctrl+9" - Control0 = "ctrl+0" - - ControlShift1 = "ctrl+shift+1" - ControlShift2 = "ctrl+shift+2" - ControlShift3 = "ctrl+shift+3" - ControlShift4 = "ctrl+shift+4" - ControlShift5 = "ctrl+shift+5" - ControlShift6 = "ctrl+shift+6" - ControlShift7 = "ctrl+shift+7" - ControlShift8 = "ctrl+shift+8" - ControlShift9 = "ctrl+shift+9" - ControlShift0 = "ctrl+shift+0" - - ControlBackslash = "ctrl+backslash" - ControlSquareClose = "ctrl+right_square_bracket" - ControlCircumflex = "ctrl+circumflex_accent" - ControlUnderscore = "ctrl+underscore" - Left = "left" Right = "right" Up = "up" Down = "down" - Home = "home" End = "end" Insert = "insert" Delete = "delete" - PageUp = "pageup" - PageDown = "pagedown" ControlLeft = "ctrl+left" ControlRight = "ctrl+right" @@ -176,19 +143,7 @@ class Key(str, Enum): ControlF23 = "ctrl+f23" ControlF24 = "ctrl+f24" - # Matches any key. - Any = "" - - # Special. - ScrollUp = "" - ScrollDown = "" - - # For internal use: key which is ignored. - # (The key binding for this key should not do anything.) - Ignore = "" - - # Some 'Key' aliases (for backward-compatibility). - ControlSpace = "ctrl-at" + ControlSpace = "ctrl-space" Tab = "tab" Space = "space" Enter = "enter" @@ -198,188 +153,181 @@ def __repr__(self) -> str: return str(self) -SINGLE_CHAR_TRANSFORMS: Mapping[str, Key] = { - "\x1b": Key.Escape, - "\t": Key.Tab, - "\n": Key.Enter, - " ": Key.Space, - "\x00": Key.ControlSpace, - "\x01": Key.ControlA, - "\x02": Key.ControlB, - "\x03": Key.ControlC, - "\x04": Key.ControlD, - "\x05": Key.ControlE, - "\x06": Key.ControlF, - "\x07": Key.ControlG, - "\x08": Key.Backspace, - "\x0B": Key.ControlK, - "\x0C": Key.ControlL, - "\x0E": Key.ControlN, - "\x0F": Key.ControlO, - "\x10": Key.ControlP, - "\x11": Key.ControlQ, - "\x12": Key.ControlR, - "\x13": Key.ControlS, - "\x14": Key.ControlT, - "\x15": Key.ControlU, - "\x16": Key.ControlV, - "\x17": Key.ControlW, - "\x18": Key.ControlX, - "\x19": Key.ControlY, - "\x1A": Key.ControlZ, - "\x7f": Key.Backspace, +SINGLE_CHAR_TRANSFORMS: Mapping[bytes, Key] = { + b"\x1b": Key.Escape, + b"\t": Key.Tab, + b"\n": Key.Enter, + b" ": Key.Space, + b"\x00": Key.ControlSpace, + b"\x01": Key.ControlA, + b"\x02": Key.ControlB, + b"\x03": Key.ControlC, + b"\x04": Key.ControlD, + b"\x05": Key.ControlE, + b"\x06": Key.ControlF, + b"\x07": Key.ControlG, + b"\x08": Key.Backspace, + b"\x0B": Key.ControlK, + b"\x0C": Key.ControlL, + b"\x0E": Key.ControlN, + b"\x0F": Key.ControlO, + b"\x10": Key.ControlP, + b"\x11": Key.ControlQ, + b"\x12": Key.ControlR, + b"\x13": Key.ControlS, + b"\x14": Key.ControlT, + b"\x15": Key.ControlU, + b"\x16": Key.ControlV, + b"\x17": Key.ControlW, + b"\x18": Key.ControlX, + b"\x19": Key.ControlY, + b"\x1A": Key.ControlZ, + b"\x7f": Key.Backspace, } -CHARS = "".join((*printable, *SINGLE_CHAR_TRANSFORMS.keys())) +single_transformable_char = char_from(b"".join((printable.encode("utf-8"), *SINGLE_CHAR_TRANSFORMS.keys()))) +decimal_digits = char_from(b"0123456789").many().map(b"".join) @generate -def single_char() -> Generator[Parser, str, AnyEvent]: - c = yield char_from(CHARS) +def single_char() -> Generator[Parser, bytes, AnyEvent]: + c = yield single_transformable_char - return KeyPressed(key=SINGLE_CHAR_TRANSFORMS.get(c, c)) + return KeyPressed(key=SINGLE_CHAR_TRANSFORMS.get(c) or c.decode("utf-8")) + + +esc = match_item(b"\x1b") +left_bracket = match_item(b"[") @generate def escape_sequence() -> Generator[Parser, AnyEvent, AnyEvent]: - keys = yield string("\x1b") >> ( - f1to4 | (string("[") >> (mouse_position | mouse_button | shift_tab | two_params | zero_or_one_params)) - ) + keys = yield esc >> (f1to4 | (left_bracket >> (mouse | two_params | zero_or_one_params))) return keys F1TO4 = { - "P": Key.F1, - "Q": Key.F2, - "R": Key.F3, - "S": Key.F4, + b"P": Key.F1, + b"Q": Key.F2, + b"R": Key.F3, + b"S": Key.F4, } - -@generate -def f1to4() -> Generator[Parser, str, AnyEvent]: - yield string("O") - final = yield char_from("PQRS") - - return KeyPressed(key=F1TO4[final]) +O_PQRS = match_item(b"O") >> char_from(b"PQRS") @generate -def shift_tab() -> Generator[Parser, str, AnyEvent]: - yield string("Z") - - return KeyPressed(key=Key.BackTab) +def f1to4() -> Generator[Parser, bytes, AnyEvent]: + final = yield O_PQRS + return KeyPressed(key=F1TO4[final]) -@generate -def mouse_position() -> Generator[Parser, str, AnyEvent]: - # https://www.xfree86.org/current/ctlseqs.html - yield string("MC") - - x_char = yield any_char - y_char = yield any_char - - x = ord(x_char) - 33 - y = ord(y_char) - 33 - return MouseMoved(position=Position(x=x, y=y)) +left_angle = match_item(b"<") +mM = char_from(b"mM") @generate -def mouse_button() -> Generator[Parser, str, AnyEvent]: +def mouse() -> Generator[Parser, bytes, AnyEvent]: # https://www.xfree86.org/current/ctlseqs.html - yield string("M") - - buttons_char = yield any_char - - buttons = ord(buttons_char) - 32 - - x_char = yield any_char - y_char = yield any_char - - x = ord(x_char) - 33 - y = ord(y_char) - 33 - p = Position(x=x, y=y) - - # TODO: handle upper bits of buttons - if buttons & 3 == 3: - return MouseUp(position=p) - elif buttons & 2 == 2: - return MouseDown(position=p, button=3) - elif buttons & 1 == 1: - return MouseDown(position=p, button=2) - else: # low bits are 00, so this is mouse down with button 1 - return MouseDown(position=p, button=1) - - -CSI_LOOKUP: Mapping[tuple[str, ...], str] = { - ("", "A"): Key.Up, - ("", "B"): Key.Down, - ("", "C"): Key.Right, - ("", "D"): Key.Left, - ("", "F"): Key.End, - ("2", "~"): Key.Insert, - ("3", "~"): Key.Delete, - ("11", "~"): Key.F1, - ("12", "~"): Key.F2, - ("13", "~"): Key.F3, - ("14", "~"): Key.F4, - ("15", "~"): Key.F5, + # https://invisible-island.net/xterm/ctlseqs/ctlseqs.pdf + yield left_angle + + buttons_ = yield decimal_digits + yield semicolon + x_ = yield decimal_digits + yield semicolon + y_ = yield decimal_digits + m = yield mM + + button_info = int(buttons_) + x = int(x_) - 1 + y = int(y_) - 1 + + pos = Position(x=x, y=y) + moving = button_info & 32 + button = (button_info & 0b11) + 1 + + if moving: + return MouseMoved(position=pos, button=button if button != 4 else None) # raw 3 is released, becomes 4 above + elif m == b"m": + return MouseUp(position=pos, button=button) + else: # m == b"M" + return MouseDown(position=pos, button=button) + + +CSI_LOOKUP: Mapping[tuple[bytes, ...], str] = { + # 0 params + (b"", b"A"): Key.Up, + (b"", b"B"): Key.Down, + (b"", b"C"): Key.Right, + (b"", b"D"): Key.Left, + (b"", b"F"): Key.End, + (b"", b"Z"): Key.BackTab, + # 1 param + (b"2", b"~"): Key.Insert, + (b"3", b"~"): Key.Delete, + (b"11", b"~"): Key.F1, + (b"12", b"~"): Key.F2, + (b"13", b"~"): Key.F3, + (b"14", b"~"): Key.F4, + (b"15", b"~"): Key.F5, # skip 16 - ("17", "~"): Key.F6, - ("18", "~"): Key.F7, - ("19", "~"): Key.F8, - ("20", "~"): Key.F9, - ("21", "~"): Key.F10, + (b"17", b"~"): Key.F6, + (b"18", b"~"): Key.F7, + (b"19", b"~"): Key.F8, + (b"20", b"~"): Key.F9, + (b"21", b"~"): Key.F10, # skip 22 - ("23", "~"): Key.F11, - ("24", "~"): Key.F12, - ("25", "~"): Key.F13, - ("26", "~"): Key.F14, - ("28", "~"): Key.F15, - ("29", "~"): Key.F16, + (b"23", b"~"): Key.F11, + (b"24", b"~"): Key.F12, + (b"25", b"~"): Key.F13, + (b"26", b"~"): Key.F14, + # skip 27 + (b"28", b"~"): Key.F15, + (b"29", b"~"): Key.F16, # skip 30 - ("31", "~"): Key.F17, - ("32", "~"): Key.F18, - ("33", "~"): Key.F19, - ("34", "~"): Key.F20, - ("1", "2", "A"): Key.ShiftUp, - ("1", "2", "B"): Key.ShiftDown, - ("1", "2", "C"): Key.ShiftRight, - ("1", "2", "D"): Key.ShiftLeft, - ("1", "5", "A"): Key.ControlUp, - ("1", "5", "B"): Key.ControlDown, - ("1", "5", "C"): Key.ControlRight, - ("1", "5", "D"): Key.ControlLeft, - ("1", "6", "A"): Key.ControlShiftUp, - ("1", "6", "B"): Key.ControlShiftDown, - ("1", "6", "C"): Key.ControlShiftRight, - ("1", "6", "D"): Key.ControlShiftLeft, - ("3", "3", "~"): Key.AltDelete, - ("3", "5", "~"): Key.ControlDelete, - ("3", "6", "~"): Key.ControlShiftInsert, + (b"31", b"~"): Key.F17, + (b"32", b"~"): Key.F18, + (b"33", b"~"): Key.F19, + (b"34", b"~"): Key.F20, + # 2 params + (b"1", b"2", b"A"): Key.ShiftUp, + (b"1", b"2", b"B"): Key.ShiftDown, + (b"1", b"2", b"C"): Key.ShiftRight, + (b"1", b"2", b"D"): Key.ShiftLeft, + (b"1", b"5", b"A"): Key.ControlUp, + (b"1", b"5", b"B"): Key.ControlDown, + (b"1", b"5", b"C"): Key.ControlRight, + (b"1", b"5", b"D"): Key.ControlLeft, + (b"1", b"6", b"A"): Key.ControlShiftUp, + (b"1", b"6", b"B"): Key.ControlShiftDown, + (b"1", b"6", b"C"): Key.ControlShiftRight, + (b"1", b"6", b"D"): Key.ControlShiftLeft, + (b"3", b"3", b"~"): Key.AltDelete, + (b"3", b"5", b"~"): Key.ControlDelete, + (b"3", b"6", b"~"): Key.ControlShiftInsert, } -FINAL_CHARS = "".join(sorted(set(key[-1] for key in CSI_LOOKUP))) +final_csi_char = char_from(b"".join(sorted(set(key[-1] for key in CSI_LOOKUP)))) +semicolon = match_item(b";") @generate -def two_params() -> Generator[Parser, str, AnyEvent]: - p1 = yield decimal_digit.many().concat() - yield string(";") - p2 = yield decimal_digit.many().concat() - e = yield char_from(FINAL_CHARS) +def two_params() -> Generator[Parser, bytes, AnyEvent]: + p1 = yield decimal_digits + yield semicolon + p2 = yield decimal_digits + e = yield final_csi_char return KeyPressed(key=CSI_LOOKUP[(p1, p2, e)]) @generate -def zero_or_one_params() -> Generator[Parser, str, AnyEvent]: - # zero params => "" - p1 = yield decimal_digit.many().concat() - - e = yield char_from(FINAL_CHARS) +def zero_or_one_params() -> Generator[Parser, bytes, AnyEvent]: + p1 = yield decimal_digits # zero params => b"" + e = yield final_csi_char return KeyPressed(key=CSI_LOOKUP[(p1, e)]) diff --git a/counterweight/output.py b/counterweight/output.py index 353d1315..d298f519 100644 --- a/counterweight/output.py +++ b/counterweight/output.py @@ -6,15 +6,19 @@ from counterweight.paint import Paint from counterweight.styles.styles import CellStyle +# https://www.xfree86.org/current/ctlseqs.html +# https://invisible-island.net/xterm/ctlseqs/ctlseqs.pdf + CURSOR_ON = "\x1b[?25h" CURSOR_OFF = "\x1b[?25l" ALT_SCREEN_ON = "\x1b[?1049h" ALT_SCREEN_OFF = "\x1b[?1049l" -# https://www.xfree86.org/current/ctlseqs.html -SET_ANY_EVENT_MOUSE = "\x1b[?1003h" -UNSET_ANY_EVENT_MOUSE = "\x1b[?1003l" +# 1003 = any event +# 1006 = sgr format +SET_ANY_EVENT_MOUSE_SGR_FORMAT = "\x1b[?1003h\x1b[?1006h" +UNSET_ANY_EVENT_MOUSE_SGR_FORMAT = "\x1b[?1003l\x1b[?1006l" CLEAR_SCREEN = "\x1b[2J" @@ -38,14 +42,14 @@ def stop_output_control(stream: TextIO) -> None: stream.flush() -def start_mouse_reporting(stream: TextIO) -> None: - stream.write(SET_ANY_EVENT_MOUSE) +def start_mouse_tracking(stream: TextIO) -> None: + stream.write(SET_ANY_EVENT_MOUSE_SGR_FORMAT) stream.flush() -def stop_mouse_reporting(stream: TextIO) -> None: - stream.write(UNSET_ANY_EVENT_MOUSE) +def stop_mouse_tracking(stream: TextIO) -> None: + stream.write(UNSET_ANY_EVENT_MOUSE_SGR_FORMAT) stream.flush() diff --git a/docs/input-handling/events.md b/docs/input-handling/events.md index eb845282..eb1f2a7f 100644 --- a/docs/input-handling/events.md +++ b/docs/input-handling/events.md @@ -8,3 +8,34 @@ ::: counterweight.events.MouseMoved ::: counterweight.events.MouseDown ::: counterweight.events.MouseUp + +## Mouse Event Semantics + +Each time the state of the mouse changes, Counterweight emits a _single_ mouse event, +one of `MouseMoved`, `MouseDown`, or `MouseUp`. + +For example, consider the following series of mouse actions and corresponding events: + +| Action | Event | +|----------------------------------------------------------------------------------|--------------------------------------------------------| +| Mouse starts at position `(0, 0)` | No event emitted | +| Mouse moves to position `(1, 0)` | `MouseMoved(position=Position(x=1, y=0), button=None)` | +| Mouse button `1` is pressed | `MouseDown(position=Position(x=1, y=0), button=1)` | +| Mouse moves to position `(1, 1)` | `MouseMoved(position=Position(x=1, y=1), button=1)` | +| Mouse button `1` is released | `MouseUp(position=Position(x=1, y=1), button=1)` | +| Mouse button `3` is pressed | `MouseDown(position=Position(x=1, y=1), button=3)` | +| Mouse moves to position `(2, 2)` and mouse button `3` is released simultaneously | `MouseUp(position=Position(x=2, y=2), button=3)` | + +!!! tip "Mouse Button Identifiers" + + Mouse buttons are identified by numbers instead of names (e.g., "left", "middle", "right") because + the button numbers are the same regardless of whether the mouse is configured for left-handed or right-handed use. + + | Number | Button for Right-Handed Mouse | Button for Left-Handed Mouse | + |--------|-------------------------------|------------------------------| + | `1` | Left | Right | + | `2` | Middle | Middle | + | `3` | Right | Left | + + You should always use the numbers instead of names to refer to mouse buttons to ensure that your application works + as expected for both left-handed and right-handed users. diff --git a/docs/reference/changelog.md b/docs/reference/changelog.md index 4798d3b4..9313daf5 100644 --- a/docs/reference/changelog.md +++ b/docs/reference/changelog.md @@ -2,9 +2,20 @@ ## Next +## `0.0.4` + +Released `2023-12-31` + +### Fixed + +- [#83](https://github.com/JoshKarpel/counterweight/pull/83) + Fixed virtual terminal escape code parsing for mouse tracking when the moues coordinates are large (>94 or so). + Mouse tracking should now work for any terminal size. + Various `Key` members that aren't currently parseable have been removed to avoid confusion. + ## `0.0.3` -Release `2023-12-30` +Released `2023-12-30` ### Changed diff --git a/mkdocs.yml b/mkdocs.yml index a4fd6138..8dfcda40 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -104,6 +104,7 @@ nav: - hooks/use_ref.md - Input Handling: - input-handling/index.md + - input-handling/events.md - input-handling/controls.md - Styles: - styles/index.md diff --git a/poetry.lock b/poetry.lock index ea6ae239..d5d0f09d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -269,63 +269,63 @@ files = [ [[package]] name = "coverage" -version = "7.3.4" +version = "7.4.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aff2bd3d585969cc4486bfc69655e862028b689404563e6b549e6a8244f226df"}, - {file = "coverage-7.3.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4353923f38d752ecfbd3f1f20bf7a3546993ae5ecd7c07fd2f25d40b4e54571"}, - {file = "coverage-7.3.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea473c37872f0159294f7073f3fa72f68b03a129799f3533b2bb44d5e9fa4f82"}, - {file = "coverage-7.3.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5214362abf26e254d749fc0c18af4c57b532a4bfde1a057565616dd3b8d7cc94"}, - {file = "coverage-7.3.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f99b7d3f7a7adfa3d11e3a48d1a91bb65739555dd6a0d3fa68aa5852d962e5b1"}, - {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:74397a1263275bea9d736572d4cf338efaade2de9ff759f9c26bcdceb383bb49"}, - {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f154bd866318185ef5865ace5be3ac047b6d1cc0aeecf53bf83fe846f4384d5d"}, - {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e0d84099ea7cba9ff467f9c6f747e3fc3906e2aadac1ce7b41add72e8d0a3712"}, - {file = "coverage-7.3.4-cp310-cp310-win32.whl", hash = "sha256:3f477fb8a56e0c603587b8278d9dbd32e54bcc2922d62405f65574bd76eba78a"}, - {file = "coverage-7.3.4-cp310-cp310-win_amd64.whl", hash = "sha256:c75738ce13d257efbb6633a049fb2ed8e87e2e6c2e906c52d1093a4d08d67c6b"}, - {file = "coverage-7.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:997aa14b3e014339d8101b9886063c5d06238848905d9ad6c6eabe533440a9a7"}, - {file = "coverage-7.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a9c5bc5db3eb4cd55ecb8397d8e9b70247904f8eca718cc53c12dcc98e59fc8"}, - {file = "coverage-7.3.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27ee94f088397d1feea3cb524e4313ff0410ead7d968029ecc4bc5a7e1d34fbf"}, - {file = "coverage-7.3.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ce03e25e18dd9bf44723e83bc202114817f3367789052dc9e5b5c79f40cf59d"}, - {file = "coverage-7.3.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85072e99474d894e5df582faec04abe137b28972d5e466999bc64fc37f564a03"}, - {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a877810ef918d0d345b783fc569608804f3ed2507bf32f14f652e4eaf5d8f8d0"}, - {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9ac17b94ab4ca66cf803f2b22d47e392f0977f9da838bf71d1f0db6c32893cb9"}, - {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:36d75ef2acab74dc948d0b537ef021306796da551e8ac8b467810911000af66a"}, - {file = "coverage-7.3.4-cp311-cp311-win32.whl", hash = "sha256:47ee56c2cd445ea35a8cc3ad5c8134cb9bece3a5cb50bb8265514208d0a65928"}, - {file = "coverage-7.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:11ab62d0ce5d9324915726f611f511a761efcca970bd49d876cf831b4de65be5"}, - {file = "coverage-7.3.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:33e63c578f4acce1b6cd292a66bc30164495010f1091d4b7529d014845cd9bee"}, - {file = "coverage-7.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:782693b817218169bfeb9b9ba7f4a9f242764e180ac9589b45112571f32a0ba6"}, - {file = "coverage-7.3.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c4277ddaad9293454da19121c59f2d850f16bcb27f71f89a5c4836906eb35ef"}, - {file = "coverage-7.3.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d892a19ae24b9801771a5a989fb3e850bd1ad2e2b6e83e949c65e8f37bc67a1"}, - {file = "coverage-7.3.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3024ec1b3a221bd10b5d87337d0373c2bcaf7afd86d42081afe39b3e1820323b"}, - {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a1c3e9d2bbd6f3f79cfecd6f20854f4dc0c6e0ec317df2b265266d0dc06535f1"}, - {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e91029d7f151d8bf5ab7d8bfe2c3dbefd239759d642b211a677bc0709c9fdb96"}, - {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6879fe41c60080aa4bb59703a526c54e0412b77e649a0d06a61782ecf0853ee1"}, - {file = "coverage-7.3.4-cp312-cp312-win32.whl", hash = "sha256:fd2f8a641f8f193968afdc8fd1697e602e199931012b574194052d132a79be13"}, - {file = "coverage-7.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:d1d0ce6c6947a3a4aa5479bebceff2c807b9f3b529b637e2b33dea4468d75fc7"}, - {file = "coverage-7.3.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:36797b3625d1da885b369bdaaa3b0d9fb8865caed3c2b8230afaa6005434aa2f"}, - {file = "coverage-7.3.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfed0ec4b419fbc807dec417c401499ea869436910e1ca524cfb4f81cf3f60e7"}, - {file = "coverage-7.3.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f97ff5a9fc2ca47f3383482858dd2cb8ddbf7514427eecf5aa5f7992d0571429"}, - {file = "coverage-7.3.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:607b6c6b35aa49defaebf4526729bd5238bc36fe3ef1a417d9839e1d96ee1e4c"}, - {file = "coverage-7.3.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8e258dcc335055ab59fe79f1dec217d9fb0cdace103d6b5c6df6b75915e7959"}, - {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a02ac7c51819702b384fea5ee033a7c202f732a2a2f1fe6c41e3d4019828c8d3"}, - {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b710869a15b8caf02e31d16487a931dbe78335462a122c8603bb9bd401ff6fb2"}, - {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c6a23ae9348a7a92e7f750f9b7e828448e428e99c24616dec93a0720342f241d"}, - {file = "coverage-7.3.4-cp38-cp38-win32.whl", hash = "sha256:758ebaf74578b73f727acc4e8ab4b16ab6f22a5ffd7dd254e5946aba42a4ce76"}, - {file = "coverage-7.3.4-cp38-cp38-win_amd64.whl", hash = "sha256:309ed6a559bc942b7cc721f2976326efbfe81fc2b8f601c722bff927328507dc"}, - {file = "coverage-7.3.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aefbb29dc56317a4fcb2f3857d5bce9b881038ed7e5aa5d3bcab25bd23f57328"}, - {file = "coverage-7.3.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:183c16173a70caf92e2dfcfe7c7a576de6fa9edc4119b8e13f91db7ca33a7923"}, - {file = "coverage-7.3.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a4184dcbe4f98d86470273e758f1d24191ca095412e4335ff27b417291f5964"}, - {file = "coverage-7.3.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93698ac0995516ccdca55342599a1463ed2e2d8942316da31686d4d614597ef9"}, - {file = "coverage-7.3.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb220b3596358a86361139edce40d97da7458412d412e1e10c8e1970ee8c09ab"}, - {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d5b14abde6f8d969e6b9dd8c7a013d9a2b52af1235fe7bebef25ad5c8f47fa18"}, - {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:610afaf929dc0e09a5eef6981edb6a57a46b7eceff151947b836d869d6d567c1"}, - {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d6ed790728fb71e6b8247bd28e77e99d0c276dff952389b5388169b8ca7b1c28"}, - {file = "coverage-7.3.4-cp39-cp39-win32.whl", hash = "sha256:c15fdfb141fcf6a900e68bfa35689e1256a670db32b96e7a931cab4a0e1600e5"}, - {file = "coverage-7.3.4-cp39-cp39-win_amd64.whl", hash = "sha256:38d0b307c4d99a7aca4e00cad4311b7c51b7ac38fb7dea2abe0d182dd4008e05"}, - {file = "coverage-7.3.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b1e0f25ae99cf247abfb3f0fac7ae25739e4cd96bf1afa3537827c576b4847e5"}, - {file = "coverage-7.3.4.tar.gz", hash = "sha256:020d56d2da5bc22a0e00a5b0d54597ee91ad72446fa4cf1b97c35022f6b6dbf0"}, + {file = "coverage-7.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36b0ea8ab20d6a7564e89cb6135920bc9188fb5f1f7152e94e8300b7b189441a"}, + {file = "coverage-7.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0676cd0ba581e514b7f726495ea75aba3eb20899d824636c6f59b0ed2f88c471"}, + {file = "coverage-7.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ca5c71a5a1765a0f8f88022c52b6b8be740e512980362f7fdbb03725a0d6b9"}, + {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7c97726520f784239f6c62506bc70e48d01ae71e9da128259d61ca5e9788516"}, + {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:815ac2d0f3398a14286dc2cea223a6f338109f9ecf39a71160cd1628786bc6f5"}, + {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:80b5ee39b7f0131ebec7968baa9b2309eddb35b8403d1869e08f024efd883566"}, + {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5b2ccb7548a0b65974860a78c9ffe1173cfb5877460e5a229238d985565574ae"}, + {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:995ea5c48c4ebfd898eacb098164b3cc826ba273b3049e4a889658548e321b43"}, + {file = "coverage-7.4.0-cp310-cp310-win32.whl", hash = "sha256:79287fd95585ed36e83182794a57a46aeae0b64ca53929d1176db56aacc83451"}, + {file = "coverage-7.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b14b4f8760006bfdb6e08667af7bc2d8d9bfdb648351915315ea17645347137"}, + {file = "coverage-7.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04387a4a6ecb330c1878907ce0dc04078ea72a869263e53c72a1ba5bbdf380ca"}, + {file = "coverage-7.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea81d8f9691bb53f4fb4db603203029643caffc82bf998ab5b59ca05560f4c06"}, + {file = "coverage-7.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74775198b702868ec2d058cb92720a3c5a9177296f75bd97317c787daf711505"}, + {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f03940f9973bfaee8cfba70ac991825611b9aac047e5c80d499a44079ec0bc"}, + {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:485e9f897cf4856a65a57c7f6ea3dc0d4e6c076c87311d4bc003f82cfe199d25"}, + {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6ae8c9d301207e6856865867d762a4b6fd379c714fcc0607a84b92ee63feff70"}, + {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bf477c355274a72435ceb140dc42de0dc1e1e0bf6e97195be30487d8eaaf1a09"}, + {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:83c2dda2666fe32332f8e87481eed056c8b4d163fe18ecc690b02802d36a4d26"}, + {file = "coverage-7.4.0-cp311-cp311-win32.whl", hash = "sha256:697d1317e5290a313ef0d369650cfee1a114abb6021fa239ca12b4849ebbd614"}, + {file = "coverage-7.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:26776ff6c711d9d835557ee453082025d871e30b3fd6c27fcef14733f67f0590"}, + {file = "coverage-7.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:13eaf476ec3e883fe3e5fe3707caeb88268a06284484a3daf8250259ef1ba143"}, + {file = "coverage-7.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846f52f46e212affb5bcf131c952fb4075b55aae6b61adc9856222df89cbe3e2"}, + {file = "coverage-7.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f66da8695719ccf90e794ed567a1549bb2644a706b41e9f6eae6816b398c4a"}, + {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:164fdcc3246c69a6526a59b744b62e303039a81e42cfbbdc171c91a8cc2f9446"}, + {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:316543f71025a6565677d84bc4df2114e9b6a615aa39fb165d697dba06a54af9"}, + {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bb1de682da0b824411e00a0d4da5a784ec6496b6850fdf8c865c1d68c0e318dd"}, + {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0e8d06778e8fbffccfe96331a3946237f87b1e1d359d7fbe8b06b96c95a5407a"}, + {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a56de34db7b7ff77056a37aedded01b2b98b508227d2d0979d373a9b5d353daa"}, + {file = "coverage-7.4.0-cp312-cp312-win32.whl", hash = "sha256:51456e6fa099a8d9d91497202d9563a320513fcf59f33991b0661a4a6f2ad450"}, + {file = "coverage-7.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd3c1e4cb2ff0083758f09be0f77402e1bdf704adb7f89108007300a6da587d0"}, + {file = "coverage-7.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9d1bf53c4c8de58d22e0e956a79a5b37f754ed1ffdbf1a260d9dcfa2d8a325e"}, + {file = "coverage-7.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:109f5985182b6b81fe33323ab4707011875198c41964f014579cf82cebf2bb85"}, + {file = "coverage-7.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc9d4bc55de8003663ec94c2f215d12d42ceea128da8f0f4036235a119c88ac"}, + {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc6d65b21c219ec2072c1293c505cf36e4e913a3f936d80028993dd73c7906b1"}, + {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a10a4920def78bbfff4eff8a05c51be03e42f1c3735be42d851f199144897ba"}, + {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b8e99f06160602bc64da35158bb76c73522a4010f0649be44a4e167ff8555952"}, + {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7d360587e64d006402b7116623cebf9d48893329ef035278969fa3bbf75b697e"}, + {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29f3abe810930311c0b5d1a7140f6395369c3db1be68345638c33eec07535105"}, + {file = "coverage-7.4.0-cp38-cp38-win32.whl", hash = "sha256:5040148f4ec43644702e7b16ca864c5314ccb8ee0751ef617d49aa0e2d6bf4f2"}, + {file = "coverage-7.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9864463c1c2f9cb3b5db2cf1ff475eed2f0b4285c2aaf4d357b69959941aa555"}, + {file = "coverage-7.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:936d38794044b26c99d3dd004d8af0035ac535b92090f7f2bb5aa9c8e2f5cd42"}, + {file = "coverage-7.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:799c8f873794a08cdf216aa5d0531c6a3747793b70c53f70e98259720a6fe2d7"}, + {file = "coverage-7.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7defbb9737274023e2d7af02cac77043c86ce88a907c58f42b580a97d5bcca9"}, + {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1526d265743fb49363974b7aa8d5899ff64ee07df47dd8d3e37dcc0818f09ed"}, + {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf635a52fc1ea401baf88843ae8708591aa4adff875e5c23220de43b1ccf575c"}, + {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:756ded44f47f330666843b5781be126ab57bb57c22adbb07d83f6b519783b870"}, + {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0eb3c2f32dabe3a4aaf6441dde94f35687224dfd7eb2a7f47f3fd9428e421058"}, + {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfd5db349d15c08311702611f3dccbef4b4e2ec148fcc636cf8739519b4a5c0f"}, + {file = "coverage-7.4.0-cp39-cp39-win32.whl", hash = "sha256:53d7d9158ee03956e0eadac38dfa1ec8068431ef8058fe6447043db1fb40d932"}, + {file = "coverage-7.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfd2a8b6b0d8e66e944d47cdec2f47c48fef2ba2f2dff5a9a75757f64172857e"}, + {file = "coverage-7.4.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:c530833afc4707fe48524a44844493f36d8727f04dcce91fb978c414a8556cc6"}, + {file = "coverage-7.4.0.tar.gz", hash = "sha256:707c0f58cb1712b8809ece32b68996ee1e609f71bd14615bd8f87a1293cb610e"}, ] [package.extras] @@ -405,13 +405,13 @@ colorama = ">=0.4" [[package]] name = "hypothesis" -version = "6.92.1" +version = "6.92.2" description = "A library for property-based testing" optional = false python-versions = ">=3.8" files = [ - {file = "hypothesis-6.92.1-py3-none-any.whl", hash = "sha256:3cba76a7389bd7245c350fcf7234663314dc81a5be0bbef72a07d8c249bfc210"}, - {file = "hypothesis-6.92.1.tar.gz", hash = "sha256:fa755ded526e50b7e2f642cdc5d64519f88d4e4ee71d9d29ec3eb2f2fddf1274"}, + {file = "hypothesis-6.92.2-py3-none-any.whl", hash = "sha256:d335044492acb03fa1fdb4edacb81cca2e578049fc7306345bc0e8947fef15a9"}, + {file = "hypothesis-6.92.2.tar.gz", hash = "sha256:841f89a486c43bdab55698de8929bd2635639ec20bf6ce98ccd75622d7ee6d41"}, ] [package.dependencies] @@ -601,16 +601,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -1107,13 +1097,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pymdown-extensions" -version = "10.5" +version = "10.7" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "pymdown_extensions-10.5-py3-none-any.whl", hash = "sha256:1f0ca8bb5beff091315f793ee17683bc1390731f6ac4c5eb01e27464b80fe879"}, - {file = "pymdown_extensions-10.5.tar.gz", hash = "sha256:1b60f1e462adbec5a1ed79dac91f666c9c0d241fa294de1989f29d20096cfd0b"}, + {file = "pymdown_extensions-10.7-py3-none-any.whl", hash = "sha256:6ca215bc57bc12bf32b414887a68b810637d039124ed9b2e5bd3325cbb2c050c"}, + {file = "pymdown_extensions-10.7.tar.gz", hash = "sha256:c0d64d5cf62566f59e6b2b690a4095c931107c250a8c8e1351c1de5f6b036deb"}, ] [package.dependencies] @@ -1242,7 +1232,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1250,15 +1239,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1275,7 +1257,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1283,7 +1264,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1477,18 +1457,18 @@ files = [ [[package]] name = "structlog" -version = "23.2.0" +version = "23.3.0" description = "Structured Logging for Python" optional = false python-versions = ">=3.8" files = [ - {file = "structlog-23.2.0-py3-none-any.whl", hash = "sha256:16a167e87b9fa7fae9a972d5d12805ef90e04857a93eba479d4be3801a6a1482"}, - {file = "structlog-23.2.0.tar.gz", hash = "sha256:334666b94707f89dbc4c81a22a8ccd34449f0201d5b1ee097a030b577fa8c858"}, + {file = "structlog-23.3.0-py3-none-any.whl", hash = "sha256:d6922a88ceabef5b13b9eda9c4043624924f60edbb00397f4d193bd754cde60a"}, + {file = "structlog-23.3.0.tar.gz", hash = "sha256:24b42b914ac6bc4a4e6f716e82ac70d7fb1e8c3b1035a765591953bfc37101a5"}, ] [package.extras] dev = ["structlog[tests,typing]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "twisted"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "sphinxext-opengraph", "twisted"] tests = ["freezegun (>=0.2.8)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "simplejson"] typing = ["mypy (>=1.4)", "rich", "twisted"] diff --git a/pyproject.toml b/pyproject.toml index 4750a32a..fc0ccf4a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "counterweight" -version = "0.0.3" +version = "0.0.4" description = "An experimental TUI framework for Python, inspired by React and Tailwind" readme="README.md" homepage="https://github.com/JoshKarpel/counterweight" diff --git a/tests/test_vt_parsing.py b/tests/test_vt_parsing.py new file mode 100644 index 00000000..de9a3f1c --- /dev/null +++ b/tests/test_vt_parsing.py @@ -0,0 +1,119 @@ +import pytest + +from counterweight.events import AnyEvent, KeyPressed, MouseDown, MouseMoved, MouseUp +from counterweight.geometry import Position +from counterweight.keys import Key, vt_inputs + + +@pytest.mark.parametrize( + "buffer, expected", + [ + # single characters, raw + (b"f", [KeyPressed(key="f")]), + (b"foo", [KeyPressed(key="f"), KeyPressed(key="o"), KeyPressed(key="o")]), + # single bytes, transformed + (b"\x1b", [KeyPressed(key=Key.Escape)]), + (b"\t", [KeyPressed(key=Key.Tab)]), + (b"\n", [KeyPressed(key=Key.Enter)]), + (b" ", [KeyPressed(key=Key.Space)]), + (b"\x00", [KeyPressed(key=Key.ControlSpace)]), + (b"\x01", [KeyPressed(key=Key.ControlA)]), + (b"\x02", [KeyPressed(key=Key.ControlB)]), + (b"\x03", [KeyPressed(key=Key.ControlC)]), + (b"\x04", [KeyPressed(key=Key.ControlD)]), + (b"\x05", [KeyPressed(key=Key.ControlE)]), + (b"\x06", [KeyPressed(key=Key.ControlF)]), + (b"\x07", [KeyPressed(key=Key.ControlG)]), + (b"\x08", [KeyPressed(key=Key.Backspace)]), + (b"\x0B", [KeyPressed(key=Key.ControlK)]), + (b"\x0C", [KeyPressed(key=Key.ControlL)]), + (b"\x0E", [KeyPressed(key=Key.ControlN)]), + (b"\x0F", [KeyPressed(key=Key.ControlO)]), + (b"\x10", [KeyPressed(key=Key.ControlP)]), + (b"\x11", [KeyPressed(key=Key.ControlQ)]), + (b"\x12", [KeyPressed(key=Key.ControlR)]), + (b"\x13", [KeyPressed(key=Key.ControlS)]), + (b"\x14", [KeyPressed(key=Key.ControlT)]), + (b"\x15", [KeyPressed(key=Key.ControlU)]), + (b"\x16", [KeyPressed(key=Key.ControlV)]), + (b"\x17", [KeyPressed(key=Key.ControlW)]), + (b"\x18", [KeyPressed(key=Key.ControlX)]), + (b"\x19", [KeyPressed(key=Key.ControlY)]), + (b"\x1A", [KeyPressed(key=Key.ControlZ)]), + (b"\x7f", [KeyPressed(key=Key.Backspace)]), + # f1 to f4 + (b"\x1bOP", [KeyPressed(key=Key.F1)]), + (b"\x1bOQ", [KeyPressed(key=Key.F2)]), + (b"\x1bOR", [KeyPressed(key=Key.F3)]), + (b"\x1bOS", [KeyPressed(key=Key.F4)]), + # shift-tab + (b"\x1b[Z", [KeyPressed(key=Key.BackTab)]), + # CSI lookups + (b"\x1b[A", [KeyPressed(key=Key.Up)]), + (b"\x1b[B", [KeyPressed(key=Key.Down)]), + (b"\x1b[C", [KeyPressed(key=Key.Right)]), + (b"\x1b[D", [KeyPressed(key=Key.Left)]), + (b"\x1b[2~", [KeyPressed(key=Key.Insert)]), + (b"\x1b[3~", [KeyPressed(key=Key.Delete)]), + (b"\x1b[F", [KeyPressed(key=Key.End)]), + (b"\x1b[11~", [KeyPressed(key=Key.F1)]), + (b"\x1b[12~", [KeyPressed(key=Key.F2)]), + (b"\x1b[13~", [KeyPressed(key=Key.F3)]), + (b"\x1b[14~", [KeyPressed(key=Key.F4)]), + (b"\x1b[17~", [KeyPressed(key=Key.F6)]), + (b"\x1b[18~", [KeyPressed(key=Key.F7)]), + (b"\x1b[19~", [KeyPressed(key=Key.F8)]), + (b"\x1b[20~", [KeyPressed(key=Key.F9)]), + (b"\x1b[21~", [KeyPressed(key=Key.F10)]), + (b"\x1b[23~", [KeyPressed(key=Key.F11)]), + (b"\x1b[24~", [KeyPressed(key=Key.F12)]), + (b"\x1b[25~", [KeyPressed(key=Key.F13)]), + (b"\x1b[26~", [KeyPressed(key=Key.F14)]), + (b"\x1b[28~", [KeyPressed(key=Key.F15)]), + (b"\x1b[29~", [KeyPressed(key=Key.F16)]), + (b"\x1b[31~", [KeyPressed(key=Key.F17)]), + (b"\x1b[32~", [KeyPressed(key=Key.F18)]), + (b"\x1b[33~", [KeyPressed(key=Key.F19)]), + (b"\x1b[34~", [KeyPressed(key=Key.F20)]), + (b"\x1b[1;2A", [KeyPressed(key=Key.ShiftUp)]), + (b"\x1b[1;2B", [KeyPressed(key=Key.ShiftDown)]), + (b"\x1b[1;2C", [KeyPressed(key=Key.ShiftRight)]), + (b"\x1b[1;2D", [KeyPressed(key=Key.ShiftLeft)]), + (b"\x1b[1;5A", [KeyPressed(key=Key.ControlUp)]), + (b"\x1b[1;5B", [KeyPressed(key=Key.ControlDown)]), + (b"\x1b[1;5C", [KeyPressed(key=Key.ControlRight)]), + (b"\x1b[1;5D", [KeyPressed(key=Key.ControlLeft)]), + (b"\x1b[1;6A", [KeyPressed(key=Key.ControlShiftUp)]), + (b"\x1b[1;6B", [KeyPressed(key=Key.ControlShiftDown)]), + (b"\x1b[1;6C", [KeyPressed(key=Key.ControlShiftRight)]), + (b"\x1b[3;3~", [KeyPressed(key=Key.AltDelete)]), + (b"\x1b[3;5~", [KeyPressed(key=Key.ControlDelete)]), + (b"\x1b[3;6~", [KeyPressed(key=Key.ControlShiftInsert)]), + # Mouse events + (b"\x1b[<35;1;1m", [MouseMoved(position=Position(x=0, y=0), button=None)]), + (b"\x1b[<35;2;1m", [MouseMoved(position=Position(x=1, y=0), button=None)]), + (b"\x1b[<32;2;1m", [MouseMoved(position=Position(x=1, y=0), button=1)]), + (b"\x1b[<33;2;1m", [MouseMoved(position=Position(x=1, y=0), button=2)]), + (b"\x1b[<34;2;1m", [MouseMoved(position=Position(x=1, y=0), button=3)]), + (b"\x1b[<35;1;2m", [MouseMoved(position=Position(x=0, y=1), button=None)]), + (b"\x1b[<35;2;2m", [MouseMoved(position=Position(x=1, y=1), button=None)]), + (b"\x1b[<35;95;1m", [MouseMoved(position=Position(x=94, y=0), button=None)]), + (b"\x1b[<35;1;95m", [MouseMoved(position=Position(x=0, y=94), button=None)]), + (b"\x1b[<35;95;95m", [MouseMoved(position=Position(x=94, y=94), button=None)]), + (b"\x1b[<35;500;500m", [MouseMoved(position=Position(x=499, y=499), button=None)]), + (b"\x1b[<35;1000;1000m", [MouseMoved(position=Position(x=999, y=999), button=None)]), + (b"\x1b[<0;1;1M", [MouseDown(position=Position(x=0, y=0), button=1)]), + (b"\x1b[<0;1;1m", [MouseUp(position=Position(x=0, y=0), button=1)]), + (b"\x1b[<1;1;1M", [MouseDown(position=Position(x=0, y=0), button=2)]), + (b"\x1b[<1;1;1m", [MouseUp(position=Position(x=0, y=0), button=2)]), + (b"\x1b[<2;1;1M", [MouseDown(position=Position(x=0, y=0), button=3)]), + (b"\x1b[<2;1;1m", [MouseUp(position=Position(x=0, y=0), button=3)]), + # It seems like some systems will use an M even in the mouse up state for motion... + (b"\x1b[<35;2;1M", [MouseMoved(position=Position(x=1, y=0), button=None)]), + (b"\x1b[<32;2;1M", [MouseMoved(position=Position(x=1, y=0), button=1)]), + (b"\x1b[<33;2;1M", [MouseMoved(position=Position(x=1, y=0), button=2)]), + (b"\x1b[<34;2;1M", [MouseMoved(position=Position(x=1, y=0), button=3)]), + ], +) +def test_vt_input_parsing(buffer: bytes, expected: list[AnyEvent]) -> None: + assert vt_inputs.parse(buffer) == expected diff --git a/tests/utils/test_partition_int.py b/tests/utils/test_partition_int.py index 03195a29..936cc2a4 100644 --- a/tests/utils/test_partition_int.py +++ b/tests/utils/test_partition_int.py @@ -43,7 +43,7 @@ def test_examples(total: int, weights: tuple[int], expected: list[int]) -> None: # but more likely terminal cells, which are even coarser. max_value=10_000, ), - weights=lists(integers(), min_size=1), + weights=lists(integers(min_value=0), min_size=1), ) def test_properties(total: int, weights: list[int]) -> None: assume(sum(weights) > 0)