mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Fix hang when removing current widget
This commit is contained in:
@@ -4,6 +4,7 @@ from contextvars import ContextVar
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .app import App
|
from .app import App
|
||||||
|
from .message_pump import MessagePump
|
||||||
|
|
||||||
|
|
||||||
class NoActiveAppError(RuntimeError):
|
class NoActiveAppError(RuntimeError):
|
||||||
@@ -11,3 +12,4 @@ class NoActiveAppError(RuntimeError):
|
|||||||
|
|
||||||
|
|
||||||
active_app: ContextVar["App"] = ContextVar("active_app")
|
active_app: ContextVar["App"] = ContextVar("active_app")
|
||||||
|
active_message_pump: ContextVar["MessagePump"] = ContextVar("active_message_pump")
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ from ._animator import DEFAULT_EASING, Animatable, Animator, EasingFunction
|
|||||||
from ._ansi_sequences import SYNC_END, SYNC_START
|
from ._ansi_sequences import SYNC_END, SYNC_START
|
||||||
from ._asyncio import create_task
|
from ._asyncio import create_task
|
||||||
from ._callback import invoke
|
from ._callback import invoke
|
||||||
from ._context import active_app
|
from ._context import active_app, active_message_pump
|
||||||
from ._event_broker import NoHandler, extract_handler_actions
|
from ._event_broker import NoHandler, extract_handler_actions
|
||||||
from ._filter import LineFilter, Monochrome
|
from ._filter import LineFilter, Monochrome
|
||||||
from ._path import _make_path_object_relative
|
from ._path import _make_path_object_relative
|
||||||
@@ -1113,6 +1113,7 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
def mount_all(
|
def mount_all(
|
||||||
self,
|
self,
|
||||||
widgets: Iterable[Widget],
|
widgets: Iterable[Widget],
|
||||||
|
*,
|
||||||
before: int | str | Widget | None = None,
|
before: int | str | Widget | None = None,
|
||||||
after: int | str | Widget | None = None,
|
after: int | str | Widget | None = None,
|
||||||
) -> AwaitMount:
|
) -> AwaitMount:
|
||||||
@@ -2103,7 +2104,7 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
"""Remove nodes from DOM, and return an awaitable that awaits cleanup.
|
"""Remove nodes from DOM, and return an awaitable that awaits cleanup.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
widgets: List of nodes to remvoe.
|
widgets: List of nodes to remove.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Awaitable that returns when the nodes have been fully removed.
|
Awaitable that returns when the nodes have been fully removed.
|
||||||
@@ -2131,13 +2132,15 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
prune_widgets_task(removed_widgets, finished_event), name="prune nodes"
|
prune_widgets_task(removed_widgets, finished_event), name="prune nodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
return AwaitRemove(finished_event)
|
await_remove = AwaitRemove(finished_event)
|
||||||
|
self.call_next(await_remove)
|
||||||
|
return await_remove
|
||||||
|
|
||||||
async def _prune_nodes(self, widgets: list[Widget]) -> None:
|
async def _prune_nodes(self, widgets: list[Widget]) -> None:
|
||||||
"""Remove nodes and children.
|
"""Remove nodes and children.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
widgets: _description_
|
widgets: Widgets to remove.
|
||||||
"""
|
"""
|
||||||
async with self._dom_lock:
|
async with self._dom_lock:
|
||||||
for widget in widgets:
|
for widget in widgets:
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ class AwaitRemove:
|
|||||||
"""
|
"""
|
||||||
self.finished_flag = finished_flag
|
self.finished_flag = finished_flag
|
||||||
|
|
||||||
|
async def __call__(self) -> None:
|
||||||
|
return await self
|
||||||
|
|
||||||
def __await__(self) -> Generator[None, None, None]:
|
def __await__(self) -> Generator[None, None, None]:
|
||||||
async def await_prune() -> None:
|
async def await_prune() -> None:
|
||||||
"""Wait for the prune operation to finish."""
|
"""Wait for the prune operation to finish."""
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from weakref import WeakSet
|
|||||||
from . import Logger, events, log, messages
|
from . import Logger, events, log, messages
|
||||||
from ._asyncio import create_task
|
from ._asyncio import create_task
|
||||||
from ._callback import invoke
|
from ._callback import invoke
|
||||||
from ._context import NoActiveAppError, active_app
|
from ._context import NoActiveAppError, active_app, active_message_pump
|
||||||
from ._time import time
|
from ._time import time
|
||||||
from .case import camel_to_snake
|
from .case import camel_to_snake
|
||||||
from .errors import DuplicateKeyHandlers
|
from .errors import DuplicateKeyHandlers
|
||||||
@@ -313,8 +313,13 @@ class MessagePump(metaclass=MessagePumpMeta):
|
|||||||
Reactive._reset_object(self)
|
Reactive._reset_object(self)
|
||||||
await self._message_queue.put(None)
|
await self._message_queue.put(None)
|
||||||
if wait and self._task is not None and asyncio.current_task() != self._task:
|
if wait and self._task is not None and asyncio.current_task() != self._task:
|
||||||
# Ensure everything is closed before returning
|
try:
|
||||||
await self._task
|
running_widget = active_message_pump.get()
|
||||||
|
except LookupError:
|
||||||
|
await self._task
|
||||||
|
else:
|
||||||
|
if running_widget is not self:
|
||||||
|
await self._task
|
||||||
|
|
||||||
def _start_messages(self) -> None:
|
def _start_messages(self) -> None:
|
||||||
"""Start messages task."""
|
"""Start messages task."""
|
||||||
@@ -382,7 +387,7 @@ class MessagePump(metaclass=MessagePumpMeta):
|
|||||||
break
|
break
|
||||||
|
|
||||||
self._active_message = message
|
self._active_message = message
|
||||||
|
context_token = active_message_pump.set(self)
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
await self._dispatch_message(message)
|
await self._dispatch_message(message)
|
||||||
@@ -415,6 +420,7 @@ class MessagePump(metaclass=MessagePumpMeta):
|
|||||||
self.app._handle_exception(error)
|
self.app._handle_exception(error)
|
||||||
break
|
break
|
||||||
finally:
|
finally:
|
||||||
|
active_message_pump.reset(context_token)
|
||||||
self._active_message = None
|
self._active_message = None
|
||||||
|
|
||||||
async def _flush_next_callbacks(self) -> None:
|
async def _flush_next_callbacks(self) -> None:
|
||||||
|
|||||||
@@ -618,6 +618,33 @@ class Widget(DOMNode):
|
|||||||
self.call_next(await_mount)
|
self.call_next(await_mount)
|
||||||
return await_mount
|
return await_mount
|
||||||
|
|
||||||
|
def mount_all(
|
||||||
|
self,
|
||||||
|
widgets: Iterable[Widget],
|
||||||
|
*,
|
||||||
|
before: int | str | Widget | None = None,
|
||||||
|
after: int | str | Widget | None = None,
|
||||||
|
) -> AwaitMount:
|
||||||
|
"""Mount widgets from an iterable.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
widgets: An iterable of widgets.
|
||||||
|
before: Optional location to mount before.
|
||||||
|
after: Optional location to mount after.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An awaitable object that waits for widgets to be mounted.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
MountError: If there is a problem with the mount request.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Only one of ``before`` or ``after`` can be provided. If both are
|
||||||
|
provided a ``MountError`` will be raised.
|
||||||
|
"""
|
||||||
|
await_mount = self.mount(*widgets, before=before, after=after)
|
||||||
|
return await_mount
|
||||||
|
|
||||||
def move_child(
|
def move_child(
|
||||||
self,
|
self,
|
||||||
child: int | Widget,
|
child: int | Widget,
|
||||||
|
|||||||
Reference in New Issue
Block a user