summaryrefslogtreecommitdiffstats
path: root/contrib/python/textual
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/python/textual')
-rw-r--r--contrib/python/textual/.dist-info/METADATA2
-rw-r--r--contrib/python/textual/textual/_compositor.py7
-rw-r--r--contrib/python/textual/textual/_xterm_parser.py86
-rw-r--r--contrib/python/textual/textual/pilot.py2
-rw-r--r--contrib/python/textual/textual/screen.py15
-rw-r--r--contrib/python/textual/textual/widgets/_input.py12
-rw-r--r--contrib/python/textual/textual/widgets/_text_area.py6
-rw-r--r--contrib/python/textual/ya.make2
8 files changed, 85 insertions, 47 deletions
diff --git a/contrib/python/textual/.dist-info/METADATA b/contrib/python/textual/.dist-info/METADATA
index f3bb97ae79a..bdcd6666e66 100644
--- a/contrib/python/textual/.dist-info/METADATA
+++ b/contrib/python/textual/.dist-info/METADATA
@@ -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 --git a/contrib/python/textual/textual/_compositor.py b/contrib/python/textual/textual/_compositor.py
index ee4c231f240..9af38a23c7e 100644
--- a/contrib/python/textual/textual/_compositor.py
+++ b/contrib/python/textual/textual/_compositor.py
@@ -923,11 +923,8 @@ class Compositor:
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 --git a/contrib/python/textual/textual/_xterm_parser.py b/contrib/python/textual/textual/_xterm_parser.py
index eafa7414ecb..df9112370cd 100644
--- a/contrib/python/textual/textual/_xterm_parser.py
+++ b/contrib/python/textual/textual/_xterm_parser.py
@@ -39,7 +39,7 @@ SPECIAL_SEQUENCES = {BRACKETED_PASTE_START, BRACKETED_PASTE_END, FOCUSIN, FOCUSO
"""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 @@ class XTermParser(Parser[Message]):
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 @@ class XTermParser(Parser[Message]):
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 @@ class XTermParser(Parser[Message]):
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 --git a/contrib/python/textual/textual/pilot.py b/contrib/python/textual/textual/pilot.py
index c387b258276..76adae9d594 100644
--- a/contrib/python/textual/textual/pilot.py
+++ b/contrib/python/textual/textual/pilot.py
@@ -438,7 +438,7 @@ class Pilot(Generic[ReturnType]):
)
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 --git a/contrib/python/textual/textual/screen.py b/contrib/python/textual/textual/screen.py
index af9070538e7..c8b1d1462d4 100644
--- a/contrib/python/textual/textual/screen.py
+++ b/contrib/python/textual/textual/screen.py
@@ -1838,8 +1838,11 @@ class Screen(Generic[ScreenResultType], Widget):
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 @@ class Screen(Generic[ScreenResultType], Widget):
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 @@ class Screen(Generic[ScreenResultType], Widget):
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 --git a/contrib/python/textual/textual/widgets/_input.py b/contrib/python/textual/textual/widgets/_input.py
index 3aa2a945bdb..bcf2c2994de 100644
--- a/contrib/python/textual/textual/widgets/_input.py
+++ b/contrib/python/textual/textual/widgets/_input.py
@@ -122,9 +122,14 @@ class Input(ScrollView):
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 @@ class Input(ScrollView):
| 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 @@ class Input(ScrollView):
| 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 --git a/contrib/python/textual/textual/widgets/_text_area.py b/contrib/python/textual/textual/widgets/_text_area.py
index d970a31c5a7..1ca74675e54 100644
--- a/contrib/python/textual/textual/widgets/_text_area.py
+++ b/contrib/python/textual/textual/widgets/_text_area.py
@@ -354,7 +354,7 @@ TextArea {
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 @@ TextArea {
show=False,
),
Binding(
- "ctrl+u",
+ "ctrl+u,super+backspace",
"delete_to_start_of_line",
"Delete to line start",
show=False,
@@ -446,7 +446,7 @@ TextArea {
| 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 --git a/contrib/python/textual/ya.make b/contrib/python/textual/ya.make
index 84a9205d17e..fd182d0171f 100644
--- a/contrib/python/textual/ya.make
+++ b/contrib/python/textual/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(8.2.7)
+VERSION(8.2.8)
LICENSE(MIT)