diff --git a/docs/examples/guide/command_palette/command01.py b/docs/examples/guide/command_palette/command01.py index b4026f3ec6..2e0a9f82d6 100644 --- a/docs/examples/guide/command_palette/command01.py +++ b/docs/examples/guide/command_palette/command01.py @@ -3,8 +3,6 @@ from functools import partial from pathlib import Path -from rich.syntax import Syntax - from textual.app import App, ComposeResult from textual.command import Hit, Hits, Provider from textual.containers import VerticalScroll @@ -53,6 +51,8 @@ def compose(self) -> ComposeResult: def open_file(self, path: Path) -> None: """Open and display a file with syntax highlighting.""" + from rich.syntax import Syntax + syntax = Syntax.from_path( str(path), line_numbers=True, diff --git a/src/textual/_asyncio.py b/src/textual/_asyncio.py deleted file mode 100644 index 351ff650d5..0000000000 --- a/src/textual/_asyncio.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -Compatibility layer for asyncio. -""" - -from __future__ import annotations - -import sys - -__all__ = ["create_task"] - -if sys.version_info >= (3, 8): - from asyncio import create_task - -else: - import asyncio - from asyncio import create_task as _create_task - from typing import Awaitable - - def create_task(coroutine: Awaitable, *, name: str | None = None) -> asyncio.Task: - """Schedule the execution of a coroutine object in a spawn task.""" - return _create_task(coroutine) diff --git a/src/textual/app.py b/src/textual/app.py index 630e7753ed..1f6e0255a1 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -15,7 +15,6 @@ import platform import sys import threading -import unicodedata import warnings from asyncio import Task from concurrent.futures import Future @@ -56,12 +55,10 @@ from rich.control import Control from rich.protocol import is_renderable from rich.segment import Segment, Segments -from rich.traceback import Traceback from . import Logger, LogGroup, LogVerbosity, actions, constants, events, log, messages from ._animator import DEFAULT_EASING, Animatable, Animator, EasingFunction from ._ansi_sequences import SYNC_END, SYNC_START -from ._asyncio import create_task from ._callback import invoke from ._compose import compose from ._compositor import CompositorUpdate @@ -84,7 +81,6 @@ from .drivers.headless_driver import HeadlessDriver from .features import FeatureFlag, parse_features from .file_monitor import FileMonitor -from .filter import ANSIToTruecolor, DimFilter, LineFilter, Monochrome from .geometry import Offset, Region, Size from .keys import ( REPLACED_KEYS, @@ -114,6 +110,7 @@ # Unused & ignored imports are needed for the docs to link to these objects: from .css.query import WrongType # type: ignore # noqa: F401 + from .filter import LineFilter from .message import Message from .pilot import Pilot from .widget import MountError # type: ignore # noqa: F401 @@ -385,11 +382,15 @@ def __init__( environ = dict(os.environ) no_color = environ.pop("NO_COLOR", None) if no_color is not None: + from .filter import Monochrome + self._filters.append(Monochrome()) for filter_name in constants.FILTERS.split(","): filter = filter_name.lower().strip() if filter == "dim": + from .filter import ANSIToTruecolor, DimFilter + self._filters.append(ANSIToTruecolor(terminal_theme.DIMMED_MONOKAI)) self._filters.append(DimFilter()) @@ -1137,6 +1138,8 @@ def get_key_display(self, key: str) -> str: async def _press_keys(self, keys: Iterable[str]) -> None: """A task to send key events.""" + import unicodedata + app = self driver = app._driver assert driver is not None @@ -1282,7 +1285,7 @@ async def run_app(app: App) -> None: # Launch the app in the "background" active_message_pump.set(app) - app_task = create_task(run_app(app), name=f"run_test {app}") + app_task = asyncio.create_task(run_app(app), name=f"run_test {app}") # Wait until the app has performed all startup routines. await app_ready_event.wait() @@ -1353,7 +1356,7 @@ async def run_auto_pilot( pilot = Pilot(app) active_message_pump.set(self) - auto_pilot_task = create_task( + auto_pilot_task = asyncio.create_task( run_auto_pilot(auto_pilot, pilot), name=repr(pilot) ) @@ -2098,6 +2101,8 @@ def _handle_exception(self, error: Exception) -> None: def _fatal_error(self) -> None: """Exits the app after an unhandled exception.""" + from rich.traceback import Traceback + self.bell() traceback = Traceback( show_locals=True, width=None, locals_max_length=5, suppress=[rich] @@ -2912,7 +2917,7 @@ async def prune_widgets_task( removed_widgets = self._detach_from_dom(widgets) finished_event = asyncio.Event() - remove_task = create_task( + remove_task = asyncio.create_task( prune_widgets_task(removed_widgets, finished_event), name="prune nodes" ) diff --git a/src/textual/command.py b/src/textual/command.py index bae3bd495c..f651ab8ab7 100644 --- a/src/textual/command.py +++ b/src/textual/command.py @@ -7,7 +7,15 @@ from __future__ import annotations from abc import ABC, abstractmethod -from asyncio import CancelledError, Queue, Task, TimeoutError, wait, wait_for +from asyncio import ( + CancelledError, + Queue, + Task, + TimeoutError, + create_task, + wait, + wait_for, +) from dataclasses import dataclass from functools import total_ordering from time import monotonic @@ -19,11 +27,9 @@ from rich.emoji import Emoji from rich.style import Style from rich.text import Text -from rich.traceback import Traceback from typing_extensions import Final, TypeAlias from . import on, work -from ._asyncio import create_task from .binding import Binding, BindingType from .containers import Horizontal, Vertical from .events import Click, Mount @@ -165,6 +171,8 @@ async def post_init_task() -> None: try: await self.startup() except Exception: + from rich.traceback import Traceback + self.app.log.error(Traceback()) else: self._init_success = True @@ -211,6 +219,8 @@ async def _shutdown(self) -> None: try: await self.shutdown() except Exception: + from rich.traceback import Traceback + self.app.log.error(Traceback()) async def shutdown(self) -> None: @@ -681,6 +691,8 @@ async def _search_for(self, search_value: str) -> AsyncGenerator[Hit, bool]: if search.done(): exception = search.exception() if exception is not None: + from rich.traceback import Traceback + self.log.error( Traceback.from_exception( type(exception), exception, exception.__traceback__ diff --git a/src/textual/css/_help_renderables.py b/src/textual/css/_help_renderables.py index bce6a9a60a..5957162f53 100644 --- a/src/textual/css/_help_renderables.py +++ b/src/textual/css/_help_renderables.py @@ -7,7 +7,6 @@ from rich.highlighter import ReprHighlighter from rich.markup import render from rich.text import Text -from rich.tree import Tree _highlighter = ReprHighlighter() @@ -89,6 +88,8 @@ def __str__(self) -> str: def __rich_console__( self, console: Console, options: ConsoleOptions ) -> RenderResult: + from rich.tree import Tree + tree = Tree(_markup_and_highlight(f"[b blue]{self.summary}"), guide_style="dim") if self.bullets is not None: for bullet in self.bullets: diff --git a/src/textual/css/errors.py b/src/textual/css/errors.py index 2daa7e3b34..815b11394e 100644 --- a/src/textual/css/errors.py +++ b/src/textual/css/errors.py @@ -1,7 +1,6 @@ from __future__ import annotations from rich.console import Console, ConsoleOptions, RenderResult -from rich.traceback import Traceback from ._help_renderables import HelpText from .tokenizer import Token, TokenError @@ -38,6 +37,8 @@ def __init__(self, *args: object, help_text: HelpText | None = None): def __rich_console__( self, console: Console, options: ConsoleOptions ) -> RenderResult: + from rich.traceback import Traceback + yield Traceback.from_exception(type(self), self, self.__traceback__) if self.help_text is not None: yield "" diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index 5d355d1e45..b7d1b5b82d 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -12,7 +12,6 @@ from rich.markup import render from rich.padding import Padding from rich.panel import Panel -from rich.syntax import Syntax from rich.text import Text from .._cache import LRUCache @@ -45,6 +44,8 @@ def __init__(self, rules: list[RuleSet]) -> None: @classmethod def _get_snippet(cls, code: str, line_no: int) -> RenderableType: + from rich.syntax import Syntax + syntax = Syntax( code, lexer="scss", diff --git a/src/textual/css/tokenizer.py b/src/textual/css/tokenizer.py index 875537eb7e..efb68e883d 100644 --- a/src/textual/css/tokenizer.py +++ b/src/textual/css/tokenizer.py @@ -8,7 +8,6 @@ from rich.highlighter import ReprHighlighter from rich.padding import Padding from rich.panel import Panel -from rich.syntax import Syntax from rich.text import Text from ..suggestions import get_suggestion @@ -51,6 +50,8 @@ def _get_snippet(self) -> Panel: Returns: A renderable. """ + from rich.syntax import Syntax + line_no = self.start[0] # TODO: Highlight column number syntax = Syntax( diff --git a/src/textual/drivers/linux_driver.py b/src/textual/drivers/linux_driver.py index cfc0c5d09d..9b4b7c9da5 100644 --- a/src/textual/drivers/linux_driver.py +++ b/src/textual/drivers/linux_driver.py @@ -12,7 +12,6 @@ from typing import TYPE_CHECKING, Any import rich.repr -import rich.traceback from .. import events from .._xterm_parser import XTermParser @@ -242,6 +241,8 @@ def _run_input_thread(self) -> None: try: self.run_input_thread() except BaseException as error: + import rich.traceback + self._app.call_later( self._app.panic, rich.traceback.Traceback(), diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py index 018b009e6c..808422cdce 100644 --- a/src/textual/message_pump.py +++ b/src/textual/message_pump.py @@ -12,14 +12,13 @@ import asyncio import inspect import threading -from asyncio import CancelledError, Queue, QueueEmpty, Task +from asyncio import CancelledError, Queue, QueueEmpty, Task, create_task from contextlib import contextmanager from functools import partial from typing import TYPE_CHECKING, Any, Awaitable, Callable, Generator, Iterable, cast from weakref import WeakSet from . import Logger, events, log, messages -from ._asyncio import create_task from ._callback import invoke from ._context import NoActiveAppError, active_app, active_message_pump from ._context import message_hook as message_hook_context_var diff --git a/src/textual/timer.py b/src/textual/timer.py index ef918e7cb5..1d31af4b14 100644 --- a/src/textual/timer.py +++ b/src/textual/timer.py @@ -7,13 +7,12 @@ from __future__ import annotations import weakref -from asyncio import CancelledError, Event, Task +from asyncio import CancelledError, Event, Task, create_task from typing import Any, Awaitable, Callable, Union from rich.repr import Result, rich_repr from . import _time, events -from ._asyncio import create_task from ._callback import invoke from ._context import active_app from ._time import sleep diff --git a/src/textual/widget.py b/src/textual/widget.py index f786cc5970..e9da4f29a2 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -4,7 +4,7 @@ from __future__ import annotations -from asyncio import wait +from asyncio import create_task, wait from collections import Counter from fractions import Fraction from itertools import islice @@ -37,13 +37,11 @@ from rich.segment import Segment from rich.style import Style from rich.text import Text -from rich.traceback import Traceback from typing_extensions import Self from . import constants, errors, events, messages from ._animator import DEFAULT_EASING, Animatable, BoundAnimator, EasingFunction from ._arrange import DockArrangeResult, arrange -from ._asyncio import create_task from ._cache import FIFOCache from ._compose import compose from ._context import NoActiveAppError, active_app @@ -3305,6 +3303,8 @@ async def _on_compose(self) -> None: f"{self!r} compose() method returned an invalid result; {error}" ) from error except Exception: + from rich.traceback import Traceback + self.app.panic(Traceback()) else: self._extend_compose(widgets) diff --git a/src/textual/widgets/_markdown.py b/src/textual/widgets/_markdown.py index 9f0af715d5..fa22634f8c 100644 --- a/src/textual/widgets/_markdown.py +++ b/src/textual/widgets/_markdown.py @@ -7,7 +7,6 @@ from markdown_it.token import Token from rich import box from rich.style import Style -from rich.syntax import Syntax from rich.table import Table from rich.text import Text from typing_extensions import TypeAlias @@ -503,6 +502,8 @@ def __init__(self, markdown: Markdown, code: str, lexer: str) -> None: super().__init__(markdown) def compose(self) -> ComposeResult: + from rich.syntax import Syntax + yield Static( Syntax( self.code, diff --git a/src/textual/worker.py b/src/textual/worker.py index 6e8a0234c3..d858fbe8c5 100644 --- a/src/textual/worker.py +++ b/src/textual/worker.py @@ -21,7 +21,6 @@ ) import rich.repr -from rich.traceback import Traceback from typing_extensions import TypeAlias from .message import Message @@ -369,6 +368,8 @@ async def _run(self, app: App) -> None: self.state = WorkerState.ERROR self._error = error app.log.worker(self, "failed", repr(error)) + from rich.traceback import Traceback + app.log.worker(Traceback()) if self.exit_on_error: app._fatal_error()