Skip to content

Commit 62f5e08

Browse files
authored
Merge branch 'main' into main
2 parents 56d7314 + fb88c07 commit 62f5e08

File tree

12 files changed

+96
-27
lines changed

12 files changed

+96
-27
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1414
### Fixed
1515

1616
- Fixed `Input` cursor color display in ANSI mode (`ansi_color=True`) https://github.com/Textualize/textual/issues/6234
17+
- Fixed alt modifier on systems without extended Key Protocol https://github.com/Textualize/textual/pull/6267
18+
- Fixed an issue where alpha keys with modifiers weren't lower cased. If you have bound to something like `ctrl+A`, then change to `ctrl+shift+a` https://github.com/Textualize/textual/pull/6267
19+
- Fixed exception when setting `loading` attribute before mount https://github.com/Textualize/textual/pull/6268
20+
- Fixed issue with dim filter not using background (may cause snapshot failures) https://github.com/Textualize/textual/pull/6269
1721

1822
## [6.7.1] - 2025-12-1
1923

src/textual/_xterm_parser.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -163,20 +163,29 @@ def on_key_token(event: events.Key) -> None:
163163
else:
164164
on_token(event)
165165

166-
def reissue_sequence_as_keys(reissue_sequence: str) -> None:
166+
def reissue_sequence_as_keys(
167+
reissue_sequence: str, process_alt: bool = False
168+
) -> None:
167169
"""Called when an escape sequence hasn't been understood.
168170
169171
Args:
170172
reissue_sequence: Key sequence to report to the app.
171173
"""
174+
175+
alt = False
176+
172177
if reissue_sequence:
173178
self.debug_log("REISSUE", repr(reissue_sequence))
174179
for character in reissue_sequence:
175-
key_events = sequence_to_key_events(character)
180+
if process_alt and character == ESC:
181+
alt = True
182+
continue
183+
key_events = sequence_to_key_events(character, alt=alt)
176184
for event in key_events:
177-
if event.key == "escape":
185+
if event.key == "escape" and not process_alt:
178186
event = events.Key("circumflex_accent", "^")
179187
on_token(event)
188+
alt = False
180189

181190
while not self.is_eof:
182191
if not bracketed_paste and paste_buffer:
@@ -211,23 +220,25 @@ def reissue_sequence_as_keys(reissue_sequence: str) -> None:
211220
# # Could be the escape key was pressed OR the start of an escape sequence
212221
sequence: str = ESC
213222

214-
def send_escape() -> None:
223+
def send_sequence(process_alt: bool = True) -> None:
215224
"""Send escape key and reissue sequence."""
216-
on_token(events.Key("escape", "\x1b"))
217-
reissue_sequence_as_keys(sequence[1:])
225+
if sequence == ESC:
226+
on_token(events.Key("escape", "\x1b"))
227+
else:
228+
reissue_sequence_as_keys(sequence, process_alt=process_alt)
218229

219230
while True:
220231
try:
221232
new_character = yield read1(constants.ESCAPE_DELAY)
222233
except ParseTimeout:
223-
send_escape()
234+
send_sequence()
224235
break
225236
except ParseEOF:
226-
send_escape()
237+
send_sequence()
227238
return
228239

229240
if new_character == ESC:
230-
send_escape()
241+
send_sequence(process_alt=False)
231242
sequence = character
232243
continue
233244
else:
@@ -313,7 +324,9 @@ def send_escape() -> None:
313324
self._debug_log_file.close()
314325
self._debug_log_file = None
315326

316-
def _sequence_to_key_events(self, sequence: str) -> Iterable[events.Key]:
327+
def _sequence_to_key_events(
328+
self, sequence: str, alt: bool = False
329+
) -> Iterable[events.Key]:
317330
"""Map a sequence of code points on to a sequence of keys.
318331
319332
Args:
@@ -342,7 +355,7 @@ def _sequence_to_key_events(self, sequence: str) -> Iterable[events.Key]:
342355
key_tokens.append(modifier)
343356

344357
key_tokens.sort()
345-
key_tokens.append(key)
358+
key_tokens.append(key.lower())
346359
yield events.Key(
347360
"+".join(key_tokens), sequence if len(sequence) == 1 else None
348361
)
@@ -376,7 +389,12 @@ def _sequence_to_key_events(self, sequence: str) -> Iterable[events.Key]:
376389
name = _character_to_key(sequence)
377390
else:
378391
name = sequence
392+
379393
name = KEY_NAME_REPLACEMENTS.get(name, name)
394+
if len(name) == 1 and alt:
395+
if name.isupper():
396+
name = f"shift+{name.lower()}"
397+
name = f"alt+{name}"
380398
yield events.Key(name, sequence)
381399
except Exception:
382400
yield events.Key(sequence, sequence)

src/textual/dom.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1623,7 +1623,7 @@ def query_ancestor(
16231623
self,
16241624
selector: str | type[QueryType],
16251625
expect_type: type[QueryType] | None = None,
1626-
) -> DOMNode | None:
1626+
) -> DOMNode:
16271627
"""Get an ancestor which matches a query.
16281628
16291629
Args:

src/textual/filter.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,17 +248,18 @@ def truecolor_style(self, style: Style, background: RichColor) -> Style:
248248
color.get_truecolor(terminal_theme, foreground=True)
249249
)
250250
changed = True
251-
if style.dim:
252-
color = dim_color(background, color)
253-
style += NO_DIM
254-
changed = True
255251

256252
if (bgcolor := style.bgcolor) is not None and bgcolor.triplet is None:
257253
bgcolor = RichColor.from_triplet(
258254
bgcolor.get_truecolor(terminal_theme, foreground=False)
259255
)
260256
changed = True
261257

258+
if style.dim and color is not None:
259+
color = dim_color(background if bgcolor is None else bgcolor, color)
260+
style += NO_DIM
261+
changed = True
262+
262263
return style + Style.from_color(color, bgcolor) if changed else style
263264

264265
def apply(self, segments: list[Segment], background: Color) -> list[Segment]:

src/textual/widget.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1035,7 +1035,10 @@ def set_loading(self, loading: bool) -> None:
10351035

10361036
def _watch_loading(self, loading: bool) -> None:
10371037
"""Called when the 'loading' reactive is changed."""
1038-
self.set_loading(loading)
1038+
if not self.is_mounted:
1039+
self.call_later(self.set_loading, loading)
1040+
else:
1041+
self.set_loading(loading)
10391042

10401043
ExpectType = TypeVar("ExpectType", bound="Widget")
10411044

src/textual/widgets/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from textual.widget import Widget
1313
from textual.widgets._button import Button
1414
from textual.widgets._checkbox import Checkbox
15-
from textual.widgets._collapsible import Collapsible, CollapsibleTitle
15+
from textual.widgets._collapsible import Collapsible
1616
from textual.widgets._content_switcher import ContentSwitcher
1717
from textual.widgets._data_table import DataTable
1818
from textual.widgets._digits import Digits
@@ -54,7 +54,6 @@
5454
"Button",
5555
"Checkbox",
5656
"Collapsible",
57-
"CollapsibleTitle",
5857
"ContentSwitcher",
5958
"DataTable",
6059
"Digits",

src/textual/widgets/__init__.pyi

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from ._button import Button as Button
33
from ._checkbox import Checkbox as Checkbox
44
from ._collapsible import Collapsible as Collapsible
5-
from ._collapsible import CollapsibleTitle as CollapsibleTitle
65
from ._content_switcher import ContentSwitcher as ContentSwitcher
76
from ._data_table import DataTable as DataTable
87
from ._digits import Digits as Digits

tests/snapshot_tests/__snapshots__/test_snapshots/test_select_expanded.svg

Lines changed: 1 addition & 1 deletion
Loading

tests/snapshot_tests/__snapshots__/test_snapshots/test_select_from_values_expanded.svg

Lines changed: 1 addition & 1 deletion
Loading

tests/snapshot_tests/__snapshots__/test_snapshots/test_select_rebuild.svg

Lines changed: 1 addition & 1 deletion
Loading

0 commit comments

Comments
 (0)