Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-textual for openSUSE:Factory checked in at 2026-07-06 12:32:50 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-textual (Old) and /work/SRC/openSUSE:Factory/.python-textual.new.1982 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-textual" Mon Jul 6 12:32:50 2026 rev:12 rq:1363872 version:8.2.8 Changes: -------- --- /work/SRC/openSUSE:Factory/python-textual/python-textual.changes 2026-05-20 15:26:00.013795864 +0200 +++ /work/SRC/openSUSE:Factory/.python-textual.new.1982/python-textual.changes 2026-07-06 12:35:07.613236989 +0200 @@ -1,0 +2,15 @@ +Sun Jul 5 10:44:42 UTC 2026 - Dirk Müller <[email protected]> + +- update to 8.2.8: + * Fixes for extended key processing, and a crash bug for + clicking the screen padding area. + * Fixed parsing Kitty extended keys with multiple codepoints + https://github.com/Textualize/textual/pull/6592 + * Fixed crash when clicking in the Screen's padding + https://github.com/Textualize/textual/pull/6598 + * super+backspace is now an alias for ctrl+u in Input and + TextArea https://github.com/Textualize/textual/pull/6594 + * alt+backspace (option+backspace on Mac) has the same effect + as ctrl+backspace in Input and TextArea + +------------------------------------------------------------------- Old: ---- textual-8.2.7.tar.gz New: ---- textual-8.2.8.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-textual.spec ++++++ --- /var/tmp/diff_new_pack.ARnNEA/_old 2026-07-06 12:35:09.049286886 +0200 +++ /var/tmp/diff_new_pack.ARnNEA/_new 2026-07-06 12:35:09.053287026 +0200 @@ -26,7 +26,7 @@ %endif %{?sle15_python_module_pythons} Name: python-textual%{psuffix} -Version: 8.2.7 +Version: 8.2.8 Release: 0 Summary: TUI framework for Python License: MIT ++++++ textual-8.2.7.tar.gz -> textual-8.2.8.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/textual-8.2.7/PKG-INFO new/textual-8.2.8/PKG-INFO --- old/textual-8.2.7/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 +++ new/textual-8.2.8/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: textual -Version: 8.2.7 +Version: 8.2.8 Summary: Modern Text User Interface framework License: MIT License-File: LICENSE diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/textual-8.2.7/pyproject.toml new/textual-8.2.8/pyproject.toml --- old/textual-8.2.7/pyproject.toml 1970-01-01 01:00:00.000000000 +0100 +++ new/textual-8.2.8/pyproject.toml 1970-01-01 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ [tool.poetry] name = "textual" -version = "8.2.7" +version = "8.2.8" homepage = "https://github.com/Textualize/textual" repository = "https://github.com/Textualize/textual" documentation = "https://textual.textualize.io/" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/textual-8.2.7/src/textual/_compositor.py new/textual-8.2.8/src/textual/_compositor.py --- old/textual-8.2.7/src/textual/_compositor.py 1970-01-01 01:00:00.000000000 +0100 +++ new/textual-8.2.8/src/textual/_compositor.py 1970-01-01 01:00:00.000000000 +0100 @@ -923,11 +923,8 @@ x -= region.x + gutter_left y -= region.y + gutter_right - if y < 0: - return None, None - - if x < 0: - return widget, Offset(0, y) + if x < 0 or y < 0: + return widget, Offset(max(0, x), max(0, y)) visible_screen_stack.set(widget.app._background_screens) line = widget.render_line(y) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/textual-8.2.7/src/textual/_xterm_parser.py new/textual-8.2.8/src/textual/_xterm_parser.py --- old/textual-8.2.7/src/textual/_xterm_parser.py 1970-01-01 01:00:00.000000000 +0100 +++ new/textual-8.2.8/src/textual/_xterm_parser.py 1970-01-01 01:00:00.000000000 +0100 @@ -39,7 +39,7 @@ """Set of special sequences.""" _re_extended_key: Final[re.Pattern[str]] = re.compile( - r"\x1b\[((?:\d*;?){2,3})([u~ABCDEFHPQRS])" + r"\x1b\[((?:[\d:]*;?){2,3})([u~ABCDEFHPQRS])" ) _re_in_band_window_resize: Final[re.Pattern[str]] = re.compile( r"\x1b\[48;(\d+(?:\:.*?)?);(\d+(?:\:.*?)?);(\d+(?:\:.*?)?);(\d+(?:\:.*?)?)t" @@ -334,8 +334,26 @@ self._debug_log_file.close() self._debug_log_file = None + @classmethod + def _parse_colon_codepoints(cls, text_str: str) -> list[str | None]: + """Convert codepoints split on colons in to a list of characters. + + Args: + text_str: String with groups of digits, separated by one or more colons. + + Returns: + A list of characters. + """ + if not text_str: + return [None] + characters: list[str | None] = [ + chr(int(part)) if part.isdecimal() else chr(1) + for part in text_str.split(":") + ] + return characters + @lru_cache(maxsize=1024) - def _parse_extended_key(self, sequence: str) -> events.Key | None: + def _parse_extended_key(self, sequence: str) -> list[events.Key] | None: """Parse a Kitty sequence. Args: @@ -348,38 +366,47 @@ if (match := _re_extended_key.fullmatch(sequence)) is None: return None + key_events: list[events.Key] = [] + codes, end = match.groups(default="") codepoint_str, modifiers_str, text_str, *_ = codes.split(";") + ["", "", ""] - codepoint = int(codepoint_str or "1") modifiers = int(modifiers_str or "0") - text = chr(int(text_str)) if text_str else None - if not (key := FUNCTIONAL_KEYS.get(f"{codepoint}{end}", "")): - key = _character_to_key(text if text else chr(codepoint)) + for text in self._parse_colon_codepoints(text_str): + if not (key := FUNCTIONAL_KEYS.get(f"{codepoint}{end}", "")): + key = _character_to_key(text if text else chr(codepoint)) - key_tokens: list[str] = [] - # The modifier is redundant on a modifier key - if modifiers and key not in MODIFIER_FUNCTIONAL_KEYS and text_str is not None: - modifier_bits = int(modifiers) - 1 - # Not convinced of the utility in reporting caps_lock and num_lock - MODIFIERS = ("alt", "ctrl", "super", "hyper", "meta") - # Ignore caps_lock and num_lock modifiers - if modifier_bits & 1 and (text is None or text.isspace()): - key_tokens.append("shift") - for bit, modifier in enumerate(MODIFIERS, 1): - if modifier == "alt" and text is not None: - continue - if modifier_bits & (1 << bit): - key_tokens.append(modifier) + key_tokens: list[str] = [] + # The modifier is redundant on a modifier key + if ( + modifiers + and key not in MODIFIER_FUNCTIONAL_KEYS + and text_str is not None + ): + modifier_bits = int(modifiers) - 1 + # Not convinced of the utility in reporting caps_lock and num_lock + MODIFIERS = ("alt", "ctrl", "super", "hyper", "meta") + # Ignore caps_lock and num_lock modifiers + if modifier_bits & 1 and (text is None or text.isspace()): + key_tokens.append("shift") + for bit, modifier in enumerate(MODIFIERS, 1): + if modifier == "alt" and text is not None: + continue + if modifier_bits & (1 << bit): + key_tokens.append(modifier) - key_tokens.sort() - if key is not None: - key_tokens.append(key) - return events.Key( - "+".join(key_tokens), - text or (None if modifiers else SPECIAL_KEY_TO_CHARACTER.get(key, None)), - ) + key_tokens.sort() + if key is not None: + key_tokens.append(key) + key_events.append( + events.Key( + "+".join(key_tokens), + text + or (None if modifiers else SPECIAL_KEY_TO_CHARACTER.get(key, None)), + ) + ) + return key_events def _sequence_to_key_events( self, sequence: str, alt: bool = False @@ -395,9 +422,10 @@ if ( not constants.DISABLE_KITTY_KEY - and (key := self._parse_extended_key(sequence)) is not None + and (keys := self._parse_extended_key(sequence)) is not None ): - yield key.copy() + for key in keys: + yield key.copy() return keys = ANSI_SEQUENCES_KEYS.get(sequence) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/textual-8.2.7/src/textual/pilot.py new/textual-8.2.8/src/textual/pilot.py --- old/textual-8.2.7/src/textual/pilot.py 1970-01-01 01:00:00.000000000 +0100 +++ new/textual-8.2.8/src/textual/pilot.py 1970-01-01 01:00:00.000000000 +0100 @@ -438,7 +438,7 @@ ) offset = Offset(message_arguments["x"], message_arguments["y"]) - if offset not in screen.region: + if offset not in screen.size.region: raise OutOfBounds( "Target offset is outside of currently-visible screen region." ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/textual-8.2.7/src/textual/screen.py new/textual-8.2.8/src/textual/screen.py --- old/textual-8.2.7/src/textual/screen.py 1970-01-01 01:00:00.000000000 +0100 +++ new/textual-8.2.8/src/textual/screen.py 1970-01-01 01:00:00.000000000 +0100 @@ -1838,8 +1838,11 @@ if select_offset is not None: content_widget = select_widget content_offset = select_offset - assert isinstance(content_widget.parent, Widget) - container = content_widget.parent + container = ( + content_widget + if isinstance(content_widget, Screen) + else content_widget.parent + ) else: content_widget = None container = select_widget @@ -1884,6 +1887,7 @@ select_widget, select_offset = self.get_widget_and_offset_at( event.x, event.y ) + if ( select_widget is not None and select_widget.allow_select @@ -1893,8 +1897,11 @@ if select_offset is not None: content_widget = select_widget content_offset = select_offset - assert isinstance(content_widget.parent, Widget) - container = content_widget.parent + container = ( + content_widget + if isinstance(content_widget, Screen) + else content_widget.parent + ) else: content_widget = None container = select_widget diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/textual-8.2.7/src/textual/widgets/_input.py new/textual-8.2.8/src/textual/widgets/_input.py --- old/textual-8.2.7/src/textual/widgets/_input.py 1970-01-01 01:00:00.000000000 +0100 +++ new/textual-8.2.8/src/textual/widgets/_input.py 1970-01-01 01:00:00.000000000 +0100 @@ -122,9 +122,14 @@ Binding( "ctrl+w", "delete_left_word", "Delete left to start of word", show=False ), - Binding("ctrl+u", "delete_left_all", "Delete all to the left", show=False), Binding( - "ctrl+backspace", + "ctrl+u,super+backspace", + "delete_left_all", + "Delete all to the left", + show=False, + ), + Binding( + "ctrl+backspace,alt+backspace", "delete_right_word", "Delete right to start of word", show=False, @@ -145,6 +150,7 @@ | shift+right | Move cursor right and select. | | ctrl+right | Move the cursor one word to the right. | | backspace | Delete the character to the left of the cursor. | + | ctrl+backspace,alt+backspace | Delete right to start of word. | | ctrl+shift+right | Move cursor right a word and select. | | ctrl+shift+a | Select all text in the input. | | home,ctrl+a | Go to the beginning of the input. | @@ -154,7 +160,7 @@ | delete,ctrl+d | Delete the character to the right of the cursor. | | enter | Submit the current value of the input. | | ctrl+w | Delete the word to the left of the cursor. | - | ctrl+u | Delete everything to the left of the cursor. | + | ctrl+u,super+backspace | Delete everything to the left of the cursor. | | ctrl+f | Delete the word to the right of the cursor. | | ctrl+k | Delete everything to the right of the cursor. | | ctrl+x | Cut selected text. | diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/textual-8.2.7/src/textual/widgets/_text_area.py new/textual-8.2.8/src/textual/widgets/_text_area.py --- old/textual-8.2.7/src/textual/widgets/_text_area.py 1970-01-01 01:00:00.000000000 +0100 +++ new/textual-8.2.8/src/textual/widgets/_text_area.py 1970-01-01 01:00:00.000000000 +0100 @@ -354,7 +354,7 @@ show=False, ), Binding( - "ctrl+w,ctrl+backspace", + "ctrl+w,ctrl+backspace,alt+backspace", "delete_word_left", "Delete left to start of word", show=False, @@ -390,7 +390,7 @@ show=False, ), Binding( - "ctrl+u", + "ctrl+u,super+backspace", "delete_to_start_of_line", "Delete to line start", show=False, @@ -446,7 +446,7 @@ | delete,ctrl+d | Delete character to the right of cursor. | | alt+delete | Delete from cursor to end of the word. | | ctrl+shift+k | Delete the current line. | - | ctrl+u | Delete from cursor to the start of the line. | + | ctrl+u,super+backspace | Delete from cursor to the start of the line. | | ctrl+k | Delete from cursor to the end of the line. | | f6 | Select the current line. | | f7 | Select all text in the document. | diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/textual-8.2.7/tests/snapshot_tests/__snapshots__/test_snapshots/test_screen_padding_select.svg new/textual-8.2.8/tests/snapshot_tests/__snapshots__/test_snapshots/test_screen_padding_select.svg --- old/textual-8.2.7/tests/snapshot_tests/__snapshots__/test_snapshots/test_screen_padding_select.svg 1970-01-01 01:00:00.000000000 +0100 +++ new/textual-8.2.8/tests/snapshot_tests/__snapshots__/test_snapshots/test_screen_padding_select.svg 1970-01-01 01:00:00.000000000 +0100 @@ -0,0 +1,150 @@ +<svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg"> + <!-- Generated with Rich https://www.textualize.io --> + <style> + + @font-face { + font-family: "Fira Code"; + src: local("FiraCode-Regular"), + url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"), + url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff"); + font-style: normal; + font-weight: 400; + } + @font-face { + font-family: "Fira Code"; + src: local("FiraCode-Bold"), + url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"), + url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff"); + font-style: bold; + font-weight: 700; + } + + .terminal-matrix { + font-family: Fira Code, monospace; + font-size: 20px; + line-height: 24.4px; + font-variant-east-asian: full-width; + } + + .terminal-title { + font-size: 18px; + font-weight: bold; + font-family: arial; + } + + .terminal-r1 { fill: #c5c8c6 } +.terminal-r2 { fill: #e0e0e0 } + </style> + + <defs> + <clipPath id="terminal-clip-terminal"> + <rect x="0" y="0" width="975.0" height="584.5999999999999" /> + </clipPath> + <clipPath id="terminal-line-0"> + <rect x="0" y="1.5" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-1"> + <rect x="0" y="25.9" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-2"> + <rect x="0" y="50.3" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-3"> + <rect x="0" y="74.7" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-4"> + <rect x="0" y="99.1" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-5"> + <rect x="0" y="123.5" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-6"> + <rect x="0" y="147.9" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-7"> + <rect x="0" y="172.3" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-8"> + <rect x="0" y="196.7" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-9"> + <rect x="0" y="221.1" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-10"> + <rect x="0" y="245.5" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-11"> + <rect x="0" y="269.9" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-12"> + <rect x="0" y="294.3" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-13"> + <rect x="0" y="318.7" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-14"> + <rect x="0" y="343.1" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-15"> + <rect x="0" y="367.5" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-16"> + <rect x="0" y="391.9" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-17"> + <rect x="0" y="416.3" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-18"> + <rect x="0" y="440.7" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-19"> + <rect x="0" y="465.1" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-20"> + <rect x="0" y="489.5" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-21"> + <rect x="0" y="513.9" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-22"> + <rect x="0" y="538.3" width="976" height="24.65"/> + </clipPath> + </defs> + + <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="992" height="633.6" rx="8"/><text class="terminal-title" fill="#c5c8c6" text-anchor="middle" x="496" y="27">CrashApp</text> + <g transform="translate(26,22)"> + <circle cx="0" cy="0" r="7" fill="#ff5f57"/> + <circle cx="22" cy="0" r="7" fill="#febc2e"/> + <circle cx="44" cy="0" r="7" fill="#28c840"/> + </g> + + <g transform="translate(9, 41)" clip-path="url(#terminal-clip-terminal)"> + <rect fill="#121212" x="0" y="1.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#094472" x="12.2" y="25.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="73.2" y="25.9" width="707.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="780.8" y="25.9" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="50.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="74.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="99.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="123.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="147.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="172.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="196.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="221.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="245.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="269.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="294.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="318.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="343.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="367.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="391.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="416.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill= "#121212" x="0" y="440.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="465.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="489.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="513.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="538.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="562.7" width="976" height="24.65" shape-rendering="crispEdges"/> + <g class="terminal-matrix"> + <text class="terminal-r1" x="976" y="20" textLength="12.2" clip-path="url(#terminal-line-0)"> +</text><text class="terminal-r2" x="12.2" y="44.4" textLength="61" clip-path="url(#terminal-line-1)">Click</text><text class="terminal-r2" x="73.2" y="44.4" textLength="707.6" clip-path="url(#terminal-line-1)"> or drag-select starting at the very left edge (column 0).</text><text class="terminal-r1" x="976" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)"> +</text><text class="terminal-r1" x="976" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)"> +</text><text class="terminal-r1" x="976" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)"> +</text><text class="terminal-r1" x="976" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)"> +</text><text class="terminal-r1" x="976" y="142" textLength="12.2" clip-path="url(#terminal-line-5)"> +</text><text class="terminal-r1" x="976" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)"> +</text><text class="terminal-r1" x="976" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)"> +</text><text class="terminal-r1" x="976" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)"> +</text><text class="terminal-r1" x="976" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> +</text><text class="terminal-r1" x="976" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> +</text><text class="terminal-r1" x="976" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> +</text><text class="terminal-r1" x="976" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> +</text><text class="terminal-r1" x="976" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> +</text><text class="terminal-r1" x="976" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> +</text><text class="terminal-r1" x="976" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> +</text><text class="terminal-r1" x="976" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> +</text><text class="terminal-r1" x="976" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> +</text><text class="terminal-r1" x="976" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> +</text><text class="terminal-r1" x="976" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> +</text><text class="terminal-r1" x="976" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> +</text><text class="terminal-r1" x="976" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> +</text><text class="terminal-r1" x="976" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> +</text> + </g> + </g> +</svg> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/textual-8.2.7/tests/snapshot_tests/__snapshots__/test_snapshots/test_screen_padding_select_move_down.svg new/textual-8.2.8/tests/snapshot_tests/__snapshots__/test_snapshots/test_screen_padding_select_move_down.svg --- old/textual-8.2.7/tests/snapshot_tests/__snapshots__/test_snapshots/test_screen_padding_select_move_down.svg 1970-01-01 01:00:00.000000000 +0100 +++ new/textual-8.2.8/tests/snapshot_tests/__snapshots__/test_snapshots/test_screen_padding_select_move_down.svg 1970-01-01 01:00:00.000000000 +0100 @@ -0,0 +1,150 @@ +<svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg"> + <!-- Generated with Rich https://www.textualize.io --> + <style> + + @font-face { + font-family: "Fira Code"; + src: local("FiraCode-Regular"), + url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"), + url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff"); + font-style: normal; + font-weight: 400; + } + @font-face { + font-family: "Fira Code"; + src: local("FiraCode-Bold"), + url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"), + url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff"); + font-style: bold; + font-weight: 700; + } + + .terminal-matrix { + font-family: Fira Code, monospace; + font-size: 20px; + line-height: 24.4px; + font-variant-east-asian: full-width; + } + + .terminal-title { + font-size: 18px; + font-weight: bold; + font-family: arial; + } + + .terminal-r1 { fill: #c5c8c6 } +.terminal-r2 { fill: #e0e0e0 } + </style> + + <defs> + <clipPath id="terminal-clip-terminal"> + <rect x="0" y="0" width="975.0" height="584.5999999999999" /> + </clipPath> + <clipPath id="terminal-line-0"> + <rect x="0" y="1.5" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-1"> + <rect x="0" y="25.9" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-2"> + <rect x="0" y="50.3" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-3"> + <rect x="0" y="74.7" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-4"> + <rect x="0" y="99.1" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-5"> + <rect x="0" y="123.5" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-6"> + <rect x="0" y="147.9" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-7"> + <rect x="0" y="172.3" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-8"> + <rect x="0" y="196.7" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-9"> + <rect x="0" y="221.1" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-10"> + <rect x="0" y="245.5" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-11"> + <rect x="0" y="269.9" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-12"> + <rect x="0" y="294.3" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-13"> + <rect x="0" y="318.7" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-14"> + <rect x="0" y="343.1" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-15"> + <rect x="0" y="367.5" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-16"> + <rect x="0" y="391.9" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-17"> + <rect x="0" y="416.3" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-18"> + <rect x="0" y="440.7" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-19"> + <rect x="0" y="465.1" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-20"> + <rect x="0" y="489.5" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-21"> + <rect x="0" y="513.9" width="976" height="24.65"/> + </clipPath> +<clipPath id="terminal-line-22"> + <rect x="0" y="538.3" width="976" height="24.65"/> + </clipPath> + </defs> + + <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="992" height="633.6" rx="8"/><text class="terminal-title" fill="#c5c8c6" text-anchor="middle" x="496" y="27">CrashApp</text> + <g transform="translate(26,22)"> + <circle cx="0" cy="0" r="7" fill="#ff5f57"/> + <circle cx="22" cy="0" r="7" fill="#febc2e"/> + <circle cx="44" cy="0" r="7" fill="#28c840"/> + </g> + + <g transform="translate(9, 41)" clip-path="url(#terminal-clip-terminal)"> + <rect fill="#121212" x="0" y="1.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#094472" x="12.2" y="25.9" width="183" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="195.2" y="25.9" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="50.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="74.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="99.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="123.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="147.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="172.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="196.7" width="976" he ight="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="221.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="245.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="269.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="294.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="318.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="343.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="367.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="391.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="416.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="440.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1 21212" x="0" y="465.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="489.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="513.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="538.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="562.7" width="976" height="24.65" shape-rendering="crispEdges"/> + <g class="terminal-matrix"> + <text class="terminal-r1" x="976" y="20" textLength="12.2" clip-path="url(#terminal-line-0)"> +</text><text class="terminal-r2" x="12.2" y="44.4" textLength="183" clip-path="url(#terminal-line-1)">Call me Ishmael</text><text class="terminal-r1" x="976" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)"> +</text><text class="terminal-r1" x="976" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)"> +</text><text class="terminal-r1" x="976" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)"> +</text><text class="terminal-r1" x="976" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)"> +</text><text class="terminal-r1" x="976" y="142" textLength="12.2" clip-path="url(#terminal-line-5)"> +</text><text class="terminal-r1" x="976" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)"> +</text><text class="terminal-r1" x="976" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)"> +</text><text class="terminal-r1" x="976" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)"> +</text><text class="terminal-r1" x="976" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> +</text><text class="terminal-r1" x="976" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> +</text><text class="terminal-r1" x="976" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> +</text><text class="terminal-r1" x="976" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> +</text><text class="terminal-r1" x="976" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> +</text><text class="terminal-r1" x="976" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> +</text><text class="terminal-r1" x="976" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> +</text><text class="terminal-r1" x="976" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> +</text><text class="terminal-r1" x="976" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> +</text><text class="terminal-r1" x="976" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> +</text><text class="terminal-r1" x="976" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> +</text><text class="terminal-r1" x="976" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> +</text><text class="terminal-r1" x="976" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> +</text><text class="terminal-r1" x="976" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> +</text> + </g> + </g> +</svg> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/textual-8.2.7/tests/snapshot_tests/test_snapshots.py new/textual-8.2.8/tests/snapshot_tests/test_snapshots.py --- old/textual-8.2.7/tests/snapshot_tests/test_snapshots.py 1970-01-01 01:00:00.000000000 +0100 +++ new/textual-8.2.8/tests/snapshot_tests/test_snapshots.py 1970-01-01 01:00:00.000000000 +0100 @@ -4936,3 +4936,61 @@ label.styles.text_opacity = f"{n * 10}%" assert snap_compare(TApp()) + + +def test_screen_padding_select(snap_compare) -> None: + """Regression test for https://github.com/Textualize/textual/issues/6596 + + When a screen has padding, clicking in the padding area should not crash + + You should see a sentence at the top of the screen, indented one cell in, and the first word (Click) highlighted. + """ + + class CrashApp(App): + # Padding on the Screen creates a 1-cell gutter at column 0 that belongs to + # the Screen itself — no child widget renders there. + CSS = "Screen { padding: 1 1 }" + + def compose(self) -> ComposeResult: + yield Static( + "Click or drag-select starting at the very left edge (column 0)." + ) + + async def run_before(pilot: Pilot) -> None: + await pilot.mouse_down(offset=(0, 1)) + await pilot.mouse_up(offset=(6, 1)) + await pilot.pause() + selected_text = pilot.app.screen.get_selected_text() + assert selected_text == "Click" + + assert snap_compare(CrashApp(), run_before=run_before) + + +def test_screen_padding_select_move_down(snap_compare) -> None: + """Regression test for https://github.com/Textualize/textual/issues/6596 + + When a screen has padding, clicking in the padding area should not crash. + + Check click and drag down. + + You should see a selected sentence at the top of the screen. + """ + + SENTENCE = "Call me Ishmael" + + class CrashApp(App): + # Padding on the Screen creates a 1-cell gutter at column 0 that belongs to + # the Screen itself — no child widget renders there. + CSS = "Screen { padding: 1 1 }" + + def compose(self) -> ComposeResult: + yield Static(SENTENCE) + + async def run_before(pilot: Pilot) -> None: + await pilot.mouse_down(offset=(10, 0)) + await pilot.mouse_up(offset=(10, 5)) + await pilot.pause() + selected_text = pilot.app.screen.get_selected_text() + assert selected_text == SENTENCE + + assert snap_compare(CrashApp(), run_before=run_before) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/textual-8.2.7/tests/test_xterm_parser.py new/textual-8.2.8/tests/test_xterm_parser.py --- old/textual-8.2.7/tests/test_xterm_parser.py 1970-01-01 01:00:00.000000000 +0100 +++ new/textual-8.2.8/tests/test_xterm_parser.py 1970-01-01 01:00:00.000000000 +0100 @@ -212,6 +212,26 @@ @pytest.mark.parametrize( + "sequence,keys", + [ + ("a", "a"), # Sanity check + ( + "\x1b[58;2;126:47u", + "~/", + ), # press option+n folloed by forwards slash, emits ~/ on international keyboards + ], +) +def test_extended_keys(parser, sequence: str, keys: str) -> None: + """Test kitty key sequences that produce multiple keys.""" + events = [] + for event in parser.feed(sequence): + assert isinstance(event, Key) + events.append(event) + parsed_keys = "".join([event.character for event in events]) + assert parsed_keys == keys + + [email protected]( "sequence, event_type, shift, meta", [ # Mouse down, with and without modifiers diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/textual-8.2.7/tests/text_area/test_edit_via_bindings.py new/textual-8.2.8/tests/text_area/test_edit_via_bindings.py --- old/textual-8.2.7/tests/text_area/test_edit_via_bindings.py 1970-01-01 01:00:00.000000000 +0100 +++ new/textual-8.2.8/tests/text_area/test_edit_via_bindings.py 1970-01-01 01:00:00.000000000 +0100 @@ -373,6 +373,18 @@ assert text_area.text == expected_result assert text_area.selection == final_selection + # Repeat with alt+backspace (alias) binding + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text(" 012 345 6789") + text_area.selection = selection + + await pilot.press("ctrl+backspace") + + assert text_area.text == expected_result + assert text_area.selection == final_selection + @pytest.mark.parametrize( "selection,expected_result,final_selection", @@ -578,7 +590,14 @@ assert text_area.text == TEXT # No change -async def test_delete_to_start_of_line_deletes_newline(): [email protected]( + "hotkey", + [ + "ctrl+u", + "super+backspace", + ], +) +async def test_delete_to_start_of_line_deletes_newline(hotkey: str): """Test that default ctrl+u deletes newline when the cursor is at the start""" class TextAreaApp(App): @@ -596,17 +615,17 @@ assert text_area.text == "Hello\nWorld" # Delete the line - await pilot.press("ctrl+u") + await pilot.press(hotkey) assert text_area.text == "Hello\n" # Delete at start, deletes the \n - await pilot.press("ctrl+u") + await pilot.press(hotkey) assert text_area.text == "Hello" # Delete the line - await pilot.press("ctrl+u") + await pilot.press(hotkey) assert text_area.text == "" # Delete empty is a NOOP - await pilot.press("ctrl+u") + await pilot.press(hotkey) assert text_area.text == ""
