Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased

### Changed

- Eager tasks are now enabled On Python3.12 and above https://github.com/Textualize/textual/pull/6102

## [6.1.0] - 2025-08-01

### Added
Expand Down
7 changes: 5 additions & 2 deletions src/textual/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2098,6 +2098,7 @@ async def run_app(app: App[ReturnType]) -> None:
await pilot._wait_for_screen()
yield pilot
finally:
await asyncio.sleep(0)
# Shutdown the app cleanly
await app._shutdown()
await app_task
Expand Down Expand Up @@ -2166,7 +2167,9 @@ async def run_auto_pilot(

self._thread_init()

app._loop = asyncio.get_running_loop()
loop = app._loop = asyncio.get_running_loop()
if hasattr(asyncio, "eager_task_factory"):
loop.set_task_factory(asyncio.eager_task_factory)
with app._context():
try:
await app._process_messages(
Expand Down Expand Up @@ -3442,7 +3445,6 @@ def _register_child(
self._registry.add(child)
child._attach(parent)
child._post_register(self)
child._start_messages()

def _register(
self,
Expand Down Expand Up @@ -3495,6 +3497,7 @@ def _register(
self._register(widget, *widget._nodes, cache=cache)
for widget in new_widgets:
apply_stylesheet(widget, cache=cache)
widget._start_messages()

if not self._running:
# If the app is not running, prevent awaiting of the widget tasks
Expand Down
11 changes: 10 additions & 1 deletion src/textual/dom.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,13 +409,22 @@ def children(self) -> Sequence["Widget"]:

@property
def displayed_children(self) -> Sequence[Widget]:
"""The displayed children (where node.display==True).
"""The displayed children (where `node.display==True`).

Returns:
A sequence of widgets.
"""
return self._nodes.displayed

@property
def displayed_and_visible_children(self) -> Sequence[Widget]:
"""The displayed children (where `node.display==True` and `node.visible==True`).

Returns:
A sequence of widgets.
"""
return self._nodes.displayed_and_visible

@property
def is_empty(self) -> bool:
"""Are there no displayed children?"""
Expand Down
3 changes: 1 addition & 2 deletions src/textual/message_pump.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,6 @@ async def _pre_process(self) -> bool:
await self._dispatch_message(events.Mount())
else:
await self._dispatch_message(events.Mount())
self.check_idle()
self._post_mount()
except Exception as error:
self.app._handle_exception(error)
Expand Down Expand Up @@ -620,7 +619,7 @@ async def _process_messages_loop(self) -> None:
"""Process messages until the queue is closed."""
_rich_traceback_guard = True
self._thread_id = threading.get_ident()

await asyncio.sleep(0)
while not self._closed:
try:
message = await self._get_message()
Expand Down
2 changes: 1 addition & 1 deletion src/textual/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -717,10 +717,10 @@ def _cover(self, widget: Widget) -> None:
self._uncover()
self._cover_widget = widget
widget._parent = self
widget._start_messages()
widget._post_register(self.app)
self.app.stylesheet.apply(widget)
self.refresh(layout=True)
widget._start_messages()

def _uncover(self) -> None:
"""Remove any widget, previously set via [`_cover`][textual.widget.Widget._cover]."""
Expand Down
2 changes: 1 addition & 1 deletion src/textual/widgets/_directory_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ def _load_directory(self, node: TreeNode[DirEntry]) -> list[Path]:
key=lambda path: (not self._safe_is_dir(path), path.name.lower()),
)

@work(exclusive=True)
@work()
async def _loader(self) -> None:
"""Background loading queue processor."""
worker = get_current_worker()
Expand Down
4 changes: 3 additions & 1 deletion src/textual/widgets/_footer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import asyncio
from collections import defaultdict
from itertools import groupby
from typing import TYPE_CHECKING
Expand Down Expand Up @@ -311,7 +312,8 @@ def _on_mouse_scroll_up(self, event: events.MouseScrollUp) -> None:
event.stop()
event.prevent_default()

def on_mount(self) -> None:
async def on_mount(self) -> None:
await asyncio.sleep(0)
self.call_next(self.bindings_changed, self.screen)

def bindings_changed(screen: Screen) -> None:
Expand Down
18 changes: 12 additions & 6 deletions src/textual/widgets/_placeholder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

from itertools import cycle
from typing import TYPE_CHECKING, Iterator
from typing import TYPE_CHECKING
from weakref import WeakKeyDictionary

from typing_extensions import Literal, Self
Expand All @@ -13,6 +13,7 @@
if TYPE_CHECKING:
from textual.app import RenderResult

from textual._context import NoActiveAppError
from textual.css._error_tools import friendly_list
from textual.reactive import Reactive, reactive
from textual.widget import Widget
Expand Down Expand Up @@ -84,7 +85,7 @@ class Placeholder(Widget):
"""

# Consecutive placeholders get assigned consecutive colors.
_COLORS: WeakKeyDictionary[App, Iterator[str]] = WeakKeyDictionary()
_COLORS: WeakKeyDictionary[App, int] = WeakKeyDictionary()
_SIZE_RENDER_TEMPLATE = "[b]{} x {}[/b]"

variant: Reactive[PlaceholderVariant] = reactive[PlaceholderVariant]("default")
Expand Down Expand Up @@ -125,17 +126,22 @@ def __init__(
self.variant = self.validate_variant(variant)
"""The current variant of the placeholder."""

try:
self._COLORS[self.app] = self._COLORS.setdefault(self.app, -1) + 1
self._color_offset = self._COLORS[self.app]
except NoActiveAppError:
self._color_offset = 0

# Set a cycle through the variants with the correct starting point.
self._variants_cycle = cycle(_VALID_PLACEHOLDER_VARIANTS_ORDERED)
while next(self._variants_cycle) != self.variant:
pass

async def _on_compose(self, event: events.Compose) -> None:
"""Set the color for this placeholder."""
colors = Placeholder._COLORS.setdefault(
self.app, cycle(_PLACEHOLDER_BACKGROUND_COLORS)
)
self.styles.background = f"{next(colors)} 50%"
color_count = len(_PLACEHOLDER_BACKGROUND_COLORS)
color = _PLACEHOLDER_BACKGROUND_COLORS[self._color_offset % color_count]
self.styles.background = f"{color} 50%"

def render(self) -> RenderResult:
"""Render the placeholder.
Expand Down
2 changes: 2 additions & 0 deletions src/textual/widgets/_toast.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ class ToastRack(Container, inherit_css=False):

DEFAULT_CSS = """
ToastRack {
display: none;
layer: _toastrack;
width: 1fr;
height: auto;
Expand Down Expand Up @@ -175,6 +176,7 @@ def show(self, notifications: Notifications) -> None:
Args:
notifications: The notifications to show.
"""
self.display = bool(notifications)
# Look for any stale toasts and remove them.
for toast in self.query(Toast):
if toast._notification not in notifications:
Expand Down
Loading
Loading