From 32c8104b3223c3b2f5eea82cae6f401b9ad93933 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Sat, 1 Feb 2025 08:53:41 +0000 Subject: [PATCH] :hammer: Move a load of code out into textual-enhanced See https://github.com/davep/textual-enhanced --- pyproject.toml | 1 + requirements-dev.lock | 3 + requirements.lock | 3 + src/peplum/app/commands/__init__.py | 2 - src/peplum/app/commands/base.py | 171 ------------------ src/peplum/app/commands/filtering.py | 4 +- src/peplum/app/commands/finding.py | 4 +- src/peplum/app/commands/main.py | 4 +- src/peplum/app/commands/navigation_sorting.py | 4 +- src/peplum/app/commands/peps_sorting.py | 4 +- src/peplum/app/peplum.py | 53 +----- src/peplum/app/providers/__init__.py | 2 - src/peplum/app/providers/authors.py | 5 +- src/peplum/app/providers/commands_provider.py | 123 ------------- src/peplum/app/providers/main.py | 5 +- src/peplum/app/providers/peps.py | 5 +- src/peplum/app/providers/python_versions.py | 5 +- src/peplum/app/providers/statuses.py | 5 +- src/peplum/app/providers/types.py | 5 +- src/peplum/app/screens/help.py | 5 +- src/peplum/app/screens/main.py | 6 +- src/peplum/app/screens/pep_viewer.py | 5 +- src/peplum/app/widgets/__init__.py | 3 +- .../app/widgets/extended_option_list.py | 104 ----------- src/peplum/app/widgets/navigation.py | 7 +- src/peplum/app/widgets/pep_details.py | 27 +-- src/peplum/app/widgets/peps_view.py | 15 +- src/peplum/app/widgets/text_viewer.py | 82 --------- 28 files changed, 89 insertions(+), 573 deletions(-) delete mode 100644 src/peplum/app/commands/base.py delete mode 100644 src/peplum/app/providers/commands_provider.py delete mode 100644 src/peplum/app/widgets/extended_option_list.py delete mode 100644 src/peplum/app/widgets/text_viewer.py diff --git a/pyproject.toml b/pyproject.toml index 36489e5..1eed57d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ dependencies = [ "packaging>=24.2", "humanize>=4.11.0", "textual-fspicker>=0.2.0", + "textual-enhanced>=0.1.0", ] readme = "README.md" requires-python = ">= 3.9" diff --git a/requirements-dev.lock b/requirements-dev.lock index f85e417..af1ca48 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -104,9 +104,12 @@ sniffio==1.3.1 textual==1.0.0 # via peplum # via textual-dev + # via textual-enhanced # via textual-fspicker # via textual-serve textual-dev==1.7.0 +textual-enhanced==0.1.0 + # via peplum textual-fspicker==0.2.0 # via peplum textual-serve==1.1.1 diff --git a/requirements.lock b/requirements.lock index 4703f31..dce01ed 100644 --- a/requirements.lock +++ b/requirements.lock @@ -48,7 +48,10 @@ sniffio==1.3.1 # via anyio textual==1.0.0 # via peplum + # via textual-enhanced # via textual-fspicker +textual-enhanced==0.1.0 + # via peplum textual-fspicker==0.2.0 # via peplum typing-extensions==4.12.2 diff --git a/src/peplum/app/commands/__init__.py b/src/peplum/app/commands/__init__.py index d964eea..4013905 100644 --- a/src/peplum/app/commands/__init__.py +++ b/src/peplum/app/commands/__init__.py @@ -7,7 +7,6 @@ ############################################################################## # Local imports. -from .base import Command from .filtering import ( Search, SearchAuthor, @@ -38,7 +37,6 @@ ############################################################################## # Exports. __all__ = [ - "Command", "ChangeTheme", "EditNotes", "Escape", diff --git a/src/peplum/app/commands/base.py b/src/peplum/app/commands/base.py deleted file mode 100644 index b72ac81..0000000 --- a/src/peplum/app/commands/base.py +++ /dev/null @@ -1,171 +0,0 @@ -"""Provides the base class for all application command messages.""" - -############################################################################## -# Backward compatibility. -from __future__ import annotations - -############################################################################## -# Python imports. -from re import Pattern, compile -from typing import Final - -############################################################################## -# Textual imports. -from textual.binding import Binding, BindingType -from textual.message import Message - - -############################################################################## -class Command(Message): - """Base class for all application command messages.""" - - COMMAND: str | None = None - """The text for the command. - - Notes: - If no `COMMAND` is provided the class name will be used. - """ - - ACTION: str | None = None - """The action to call when the command is executed. - - By default the action will be: - - `action_{snake-case-of-command}_command` - """ - - FOOTER_TEXT: str | None = None - """The text to show in the footer. - - Notes: - If no `FOOTER_TEXT` is provided the `command` will be used. - """ - - SHOW_IN_FOOTER: bool = False - """Should the command be shown in the footer?""" - - BINDING_KEY: str | tuple[str, str] | None = None - """The binding key for the command. - - This can either be a string, which is the keys to bind, a tuple of the - keys and also an overriding display value, or `None`. - """ - - _SPLITTER: Final[Pattern[str]] = compile("[A-Z]?[a-z]+|[A-Z]+(?=[A-Z]|$)") - """Regular expression for splitting up a command name.""" - - @classmethod - def command(cls) -> str: - """The text for the command. - - Returns: - The command's textual name. - """ - return cls.COMMAND or " ".join(cls._SPLITTER.findall(cls.__name__)) - - @property - def context_command(self) -> str: - """The command in context.""" - return self.command() - - @classmethod - def tooltip(cls) -> str: - """The tooltip for the command.""" - return cls.__doc__ or "" - - @property - def context_tooltip(self) -> str: - """The tooltip for the comment, in context.""" - return self.tooltip() - - @classmethod - def key_binding(cls) -> str: - """Get the key that is the binding for this command. - - Returns: - The key that is bound, or `None` if there isn't one. - - Notes: - If a command has multiple bindings, only the first key is - returned. - """ - if isinstance(key := cls.BINDING_KEY, tuple): - key, *_ = key - if isinstance(key, str): - key, *_ = cls.binding().key.partition(",") - return (key or "").strip() - - @classmethod - def action_name(cls) -> str: - """Get the action name for the command. - - Returns: - The action name. - """ - return ( - cls.ACTION - or f"{'_'.join(cls._SPLITTER.findall(cls.__name__))}_command".lower() - ) - - @staticmethod - def bindings(*bindings: BindingType | type[Command]) -> list[BindingType]: - """Create bindings. - - Args: - bindings: Normal Textual bindings or a command class. - - Returns: - A list of bindings that can be used with `BINDINGS`. - """ - return [ - (binding if isinstance(binding, (Binding, tuple)) else binding.binding()) - for binding in bindings - ] - - @classmethod - def binding(cls) -> Binding: - """Create a binding object for the command. - - Returns: - A `Binding` for the command's key bindings. - - Raises: - ValueError: If the command has no key binding. - """ - if not cls.BINDING_KEY: - raise ValueError("No binding key defined, unable to create a binding") - keys, display = ( - cls.BINDING_KEY - if isinstance(cls.BINDING_KEY, tuple) - else (cls.BINDING_KEY, None) - ) - return Binding( - keys, - cls.action_name(), - description=cls.FOOTER_TEXT or cls.command(), - tooltip=cls.tooltip(), - show=cls.SHOW_IN_FOOTER, - key_display=display, - ) - - @classmethod - def primary_binding(cls) -> Binding: - """Create a binding object for the primary key of the command. - - Returns: - A `Binding` for the primary key. - - Raises: - ValueError: If the command has no key binding. - """ - return (binding := cls.binding()).with_key( - cls.key_binding(), binding.key_display - ) - - @property - def has_binding(self) -> bool: - """Does this command have a binding?""" - return self.BINDING_KEY is not None - - -### base_command.py ends here diff --git a/src/peplum/app/commands/filtering.py b/src/peplum/app/commands/filtering.py index 75151b7..a559315 100644 --- a/src/peplum/app/commands/filtering.py +++ b/src/peplum/app/commands/filtering.py @@ -1,8 +1,8 @@ """Provides command-oriented messages that relate to filtering.""" ############################################################################## -# Local imports. -from .base import Command +# Textual enhanced imports. +from textual_enhanced.commands import Command ############################################################################## diff --git a/src/peplum/app/commands/finding.py b/src/peplum/app/commands/finding.py index 16deaa3..bccca99 100644 --- a/src/peplum/app/commands/finding.py +++ b/src/peplum/app/commands/finding.py @@ -1,8 +1,8 @@ """Commands related to finding things.""" ############################################################################## -# Local imports. -from .base import Command +# Textual enhanced imports. +from textual_enhanced.commands import Command ############################################################################## diff --git a/src/peplum/app/commands/main.py b/src/peplum/app/commands/main.py index cd5298c..1d080c8 100644 --- a/src/peplum/app/commands/main.py +++ b/src/peplum/app/commands/main.py @@ -1,8 +1,8 @@ """The main commands used within the application.""" ############################################################################## -# Local imports. -from .base import Command +# Textual enhanced imports. +from textual_enhanced.commands import Command ############################################################################## diff --git a/src/peplum/app/commands/navigation_sorting.py b/src/peplum/app/commands/navigation_sorting.py index a8369a9..dff94c6 100644 --- a/src/peplum/app/commands/navigation_sorting.py +++ b/src/peplum/app/commands/navigation_sorting.py @@ -1,8 +1,8 @@ """Commands for affecting navigation sort ordering.""" ############################################################################## -# Local imports. -from .base import Command +# Textual enhanced imports. +from textual_enhanced.commands import Command ############################################################################## diff --git a/src/peplum/app/commands/peps_sorting.py b/src/peplum/app/commands/peps_sorting.py index 7e84bec..afecbc1 100644 --- a/src/peplum/app/commands/peps_sorting.py +++ b/src/peplum/app/commands/peps_sorting.py @@ -1,8 +1,8 @@ """Provides command-oriented messages that relate to sorting PEPs.""" ############################################################################## -# Local imports. -from .base import Command +# Textual enhanced imports. +from textual_enhanced.commands import Command ############################################################################## diff --git a/src/peplum/app/peplum.py b/src/peplum/app/peplum.py index d1cc564..87f7ca5 100644 --- a/src/peplum/app/peplum.py +++ b/src/peplum/app/peplum.py @@ -2,8 +2,11 @@ ############################################################################## # Textual imports. -from textual.app import App, InvalidThemeError -from textual.binding import Binding +from textual.app import InvalidThemeError + +############################################################################## +# Textual enhanced imports. +from textual_enhanced.app import EnhancedApp ############################################################################## # Local imports. @@ -15,53 +18,9 @@ ############################################################################## -class Peplum(App[None]): +class Peplum(EnhancedApp[None]): """The main application class.""" - CSS = """ - CommandPalette > Vertical { - width: 75%; /* Full-width command palette looks like garbage. Fix that. */ - background: $panel; - SearchIcon { - display: none; - } - } - - /* Make the LoadingIndicator look less like it was just slapped on. */ - LoadingIndicator { - background: transparent; - } - - /* Remove cruft from the Header. */ - Header { - /* The header icon is ugly and pointless. Remove it. */ - HeaderIcon { - visibility: hidden; - } - - /* The tall version of the header is utterly useless. Nuke that. */ - &.-tall { - height: 1 !important; - } - } - - /* General style tweaks that affect all widgets. */ - * { - /* Let's make scrollbars a wee bit thinner. */ - scrollbar-size-vertical: 1; - } - """ - - BINDINGS = [ - Binding( - "ctrl+p, super+x, :", - "command_palette", - "Commands", - show=False, - tooltip="Show the command palette", - ), - ] - COMMANDS = set() def __init__(self) -> None: diff --git a/src/peplum/app/providers/__init__.py b/src/peplum/app/providers/__init__.py index 4b2533b..b5dae66 100644 --- a/src/peplum/app/providers/__init__.py +++ b/src/peplum/app/providers/__init__.py @@ -3,7 +3,6 @@ ############################################################################## # Local imports. from .authors import AuthorCommands -from .commands_provider import CommandsProvider from .main import MainCommands from .peps import PEPsCommands from .python_versions import PythonVersionCommands @@ -14,7 +13,6 @@ # Exports. __all__ = [ "AuthorCommands", - "CommandsProvider", "MainCommands", "PEPsCommands", "PythonVersionCommands", diff --git a/src/peplum/app/providers/authors.py b/src/peplum/app/providers/authors.py index 9c7a268..b5d47a5 100644 --- a/src/peplum/app/providers/authors.py +++ b/src/peplum/app/providers/authors.py @@ -1,10 +1,13 @@ """Author filtering commands for the command palette.""" +############################################################################## +# Textual enhanced imports. +from textual_enhanced.commands import CommandHit, CommandHits, CommandsProvider + ############################################################################## # Local imports. from ..data import PEPs from ..messages import ShowAuthor -from .commands_provider import CommandHit, CommandHits, CommandsProvider ############################################################################## diff --git a/src/peplum/app/providers/commands_provider.py b/src/peplum/app/providers/commands_provider.py deleted file mode 100644 index 4cb45ba..0000000 --- a/src/peplum/app/providers/commands_provider.py +++ /dev/null @@ -1,123 +0,0 @@ -"""Provides a base command-oriented command palette provider class.""" - -############################################################################## -# Python imports. -from abc import abstractmethod -from functools import partial -from typing import Iterator, NamedTuple, TypeAlias - -############################################################################## -# Rich imports. -from rich.text import Text - -############################################################################## -# Textual imports. -from textual.command import DiscoveryHit, Hit, Hits, Provider -from textual.message import Message - -############################################################################## -# Local imports. -from ..commands import Command - - -############################################################################## -class CommandHit(NamedTuple): - """A command hit for use in building a command palette hit.""" - - command: str - """The command.""" - description: str - """The description of the command.""" - message: Message - """The message to emit when the command is chosen.""" - - -############################################################################## -CommandHits: TypeAlias = Iterator[CommandHit | Command] -"""The result of looking for commands to make into hits.""" - - -############################################################################## -class CommandsProvider(Provider): - """A base class for command-message-oriented command palette commands.""" - - @classmethod - def prompt(cls) -> str: - """The prompt for the command provider.""" - return "" - - @abstractmethod - def commands(self) -> CommandHits: - """Provide the command data for the command palette. - - Yields: - A tuple of the command, the command description and a command message. - """ - raise NotImplementedError - - @property - def _commands(self) -> Iterator[CommandHit]: - """The commands available for the palette.""" - return ( - CommandHit(command.context_command, command.context_tooltip, command) - if isinstance(command, Command) - else command - for command in self.commands() - ) - - def _maybe_add_binding(self, message: Command | Message, text: str | Text) -> Text: - """Maybe add binding details to some text. - - Args: - message: The command message to maybe get the binding for. - text: The text to add the binding details to. - - Returns: - The resulting text. - """ - if isinstance(text, str): - text = Text(text) - if not isinstance(message, Command) or not message.has_binding: - return text - style = self.app.current_theme.accent if self.app.current_theme else None - return text.append_text(Text(" ")).append_text( - Text( - f"[{self.app.get_key_display(message.primary_binding())}]", - style=style or "dim", - ) - ) - - async def discover(self) -> Hits: - """Handle a request to discover commands. - - Yields: - Command discovery hits for the command palette. - """ - for command, description, message in self._commands: - yield DiscoveryHit( - self._maybe_add_binding(message, command), - partial(self.screen.post_message, message), - help=description, - ) - - async def search(self, query: str) -> Hits: - """Handle a request to search for commands that match the query. - - Args: - query: The query from the user. - - Yields: - Command hits for the command palette. - """ - matcher = self.matcher(query) - for command, description, message in self._commands: - if match := matcher.match(command): - yield Hit( - match, - self._maybe_add_binding(message, matcher.highlight(command)), - partial(self.screen.post_message, message), - help=description, - ) - - -### commands_provider.py ends here diff --git a/src/peplum/app/providers/main.py b/src/peplum/app/providers/main.py index 1736c0a..7f18caf 100644 --- a/src/peplum/app/providers/main.py +++ b/src/peplum/app/providers/main.py @@ -1,5 +1,9 @@ """Provides the main application commands for the command palette.""" +############################################################################## +# Textual enhanced imports. +from textual_enhanced.commands import CommandHits, CommandsProvider + ############################################################################## # Local imports. from ..commands import ( @@ -26,7 +30,6 @@ ToggleTypesSortOrder, ViewPEP, ) -from .commands_provider import CommandHits, CommandsProvider ############################################################################## diff --git a/src/peplum/app/providers/peps.py b/src/peplum/app/providers/peps.py index b33b89e..409bb3e 100644 --- a/src/peplum/app/providers/peps.py +++ b/src/peplum/app/providers/peps.py @@ -4,11 +4,14 @@ # Python imports. from operator import attrgetter +############################################################################## +# Textual enhanced imports. +from textual_enhanced.commands import CommandHit, CommandHits, CommandsProvider + ############################################################################## # Local imports. from ..data import PEPs from ..messages import GotoPEP -from .commands_provider import CommandHit, CommandHits, CommandsProvider ############################################################################## diff --git a/src/peplum/app/providers/python_versions.py b/src/peplum/app/providers/python_versions.py index df05927..bc815c2 100644 --- a/src/peplum/app/providers/python_versions.py +++ b/src/peplum/app/providers/python_versions.py @@ -1,10 +1,13 @@ """Python version filtering commands for the command palette.""" +############################################################################## +# Textual enhanced imports. +from textual_enhanced.commands import CommandHit, CommandHits, CommandsProvider + ############################################################################## # Local imports. from ..data import PEPs from ..messages import ShowPythonVersion -from .commands_provider import CommandHit, CommandHits, CommandsProvider ############################################################################## diff --git a/src/peplum/app/providers/statuses.py b/src/peplum/app/providers/statuses.py index b95e635..e8c87a3 100644 --- a/src/peplum/app/providers/statuses.py +++ b/src/peplum/app/providers/statuses.py @@ -1,10 +1,13 @@ """PEP status filtering commands for the command palette.""" +############################################################################## +# Textual enhanced imports. +from textual_enhanced.commands import CommandHit, CommandHits, CommandsProvider + ############################################################################## # Local imports. from ..data import PEPs from ..messages import ShowStatus -from .commands_provider import CommandHit, CommandHits, CommandsProvider ############################################################################## diff --git a/src/peplum/app/providers/types.py b/src/peplum/app/providers/types.py index 48b7a84..2251409 100644 --- a/src/peplum/app/providers/types.py +++ b/src/peplum/app/providers/types.py @@ -1,10 +1,13 @@ """PEP type filtering commands for the command palette.""" +############################################################################## +# Textual enhanced imports. +from textual_enhanced.commands import CommandHit, CommandHits, CommandsProvider + ############################################################################## # Local imports. from ..data import PEPs from ..messages import ShowType -from .commands_provider import CommandHit, CommandHits, CommandsProvider ############################################################################## diff --git a/src/peplum/app/screens/help.py b/src/peplum/app/screens/help.py index 56fe40e..a223130 100644 --- a/src/peplum/app/screens/help.py +++ b/src/peplum/app/screens/help.py @@ -17,10 +17,13 @@ from textual.screen import ModalScreen, Screen from textual.widgets import Button, Markdown +############################################################################## +# Textual enhanced imports. +from textual_enhanced.commands import Command + ############################################################################## # Local imports. from ... import __version__ -from ..commands import Command ############################################################################## # The help text. diff --git a/src/peplum/app/screens/main.py b/src/peplum/app/screens/main.py index e9da65e..9d51bcb 100644 --- a/src/peplum/app/screens/main.py +++ b/src/peplum/app/screens/main.py @@ -16,13 +16,16 @@ from textual.screen import Screen from textual.widgets import Footer, Header +############################################################################## +# Textual enhanced imports. +from textual_enhanced.commands import Command, CommandsProvider + ############################################################################## # Local imports. from ... import __version__ from ...peps import API from ..commands import ( ChangeTheme, - Command, EditNotes, Escape, FindPEP, @@ -68,7 +71,6 @@ ) from ..providers import ( AuthorCommands, - CommandsProvider, MainCommands, PEPsCommands, PythonVersionCommands, diff --git a/src/peplum/app/screens/pep_viewer.py b/src/peplum/app/screens/pep_viewer.py index 254196c..2ede888 100644 --- a/src/peplum/app/screens/pep_viewer.py +++ b/src/peplum/app/screens/pep_viewer.py @@ -12,6 +12,10 @@ from textual.screen import ModalScreen from textual.widgets import Button, TextArea +############################################################################## +# Textual enhanced imports. +from textual_enhanced.widgets import TextViewer + ############################################################################## # Textual fspicker imports. from textual_fspicker import FileSave @@ -20,7 +24,6 @@ # Local imports. from ...peps import API from ..data import PEP, cache_dir -from ..widgets import TextViewer from .confirm import Confirm diff --git a/src/peplum/app/widgets/__init__.py b/src/peplum/app/widgets/__init__.py index a0d2c6e..913221b 100644 --- a/src/peplum/app/widgets/__init__.py +++ b/src/peplum/app/widgets/__init__.py @@ -5,11 +5,10 @@ from .navigation import Navigation from .pep_details import PEPDetails from .peps_view import PEPsView -from .text_viewer import TextViewer ############################################################################## # Exports. -__all__ = ["Navigation", "PEPDetails", "PEPsView", "TextViewer"] +__all__ = ["Navigation", "PEPDetails", "PEPsView"] ### __init__.py ends here diff --git a/src/peplum/app/widgets/extended_option_list.py b/src/peplum/app/widgets/extended_option_list.py deleted file mode 100644 index d99ae94..0000000 --- a/src/peplum/app/widgets/extended_option_list.py +++ /dev/null @@ -1,104 +0,0 @@ -"""A version of Textual's OptionList with some more navigation options.""" - -############################################################################## -# Python imports. -from types import TracebackType - -############################################################################## -# Textual imports. -from textual.binding import Binding -from textual.geometry import Size -from textual.widgets import OptionList -from textual.widgets.option_list import OptionDoesNotExist - -############################################################################## -# Typing extension imports. -from typing_extensions import Self - - -############################################################################## -class PreservedHighlight: - """Context manager class to preserve an `OptionList` location. - - If the highlighted option has an ID, an attempt will be made to get back - to that option; otherwise we return to the option in the same location. - """ - - def __init__(self, option_list: OptionList) -> None: - """Initialise the object. - - Args: - option_list: The `OptionList` to preserve the location for. - """ - self._option_list = option_list - """The option list we're preserving the location for.""" - self._highlighted = option_list.highlighted - """The highlight that we should try to go back to.""" - self._option_id = ( - option_list.get_option_at_index(self._highlighted).id - if self._highlighted is not None - else None - ) - """The ID of the option to try and get back to, or `None`.""" - - def __enter__(self) -> Self: - """Handle entry to the context.""" - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_traceback: TracebackType | None, - ) -> None: - """Handle exit from the context.""" - del exc_type, exc_val, exc_traceback - # Attempt to get back to the same option, or an option in a similar - # location. - try: - self._option_list.highlighted = ( - self._highlighted - if self._option_id is None - else self._option_list.get_option_index(self._option_id) - ) - except OptionDoesNotExist: - self._option_list.highlighted = self._highlighted - # If we still haven't landed anywhere and there are options, select - # the first one. - if self._option_list.highlighted is None and self._option_list.option_count: - self._option_list.highlighted = 0 - - -############################################################################## -class OptionListEx(OptionList): - """The Textual `OptionList` with more features.""" - - DEFAULT_CSS = """ - OptionListEx { - &:focus { - background-tint: initial; - } - } - """ - - BINDINGS = [ - Binding("j, right", "cursor_down", show=False), - Binding("k, left", "cursor_up", show=False), - Binding("<", "first", show=False), - Binding(">", "last", show=False), - Binding("space", "page_down", show=False), - ] - - @property - def preserved_highlight(self) -> PreservedHighlight: - """Provides a context that preserves the highlight location.""" - return PreservedHighlight(self) - - def get_content_width(self, container: Size, viewport: Size) -> int: - """Workaround for https://github.com/Textualize/textual/issues/5489""" - return ( - super().get_content_width(container, viewport) if self.option_count else 0 - ) - - -### extended_option_list.py ends here diff --git a/src/peplum/app/widgets/navigation.py b/src/peplum/app/widgets/navigation.py index 056f237..5da5c94 100644 --- a/src/peplum/app/widgets/navigation.py +++ b/src/peplum/app/widgets/navigation.py @@ -20,6 +20,10 @@ from textual.widgets import OptionList from textual.widgets.option_list import Option +############################################################################## +# Textual enhanced imports. +from textual_enhanced.widgets import EnhancedOptionList + ############################################################################## # Typing exception imports. from typing_extensions import Self @@ -37,7 +41,6 @@ TypeCount, ) from ..messages import ShowAuthor, ShowPythonVersion, ShowStatus, ShowType -from .extended_option_list import OptionListEx ############################################################################## @@ -197,7 +200,7 @@ def command(self) -> Message: ############################################################################## -class Navigation(OptionListEx): +class Navigation(EnhancedOptionList): """The main navigation panel.""" HELP = """ diff --git a/src/peplum/app/widgets/pep_details.py b/src/peplum/app/widgets/pep_details.py index 1b7dd38..eb97ba3 100644 --- a/src/peplum/app/widgets/pep_details.py +++ b/src/peplum/app/widgets/pep_details.py @@ -26,6 +26,10 @@ from textual.widgets import Label, Markdown from textual.widgets.option_list import Option +############################################################################## +# Textual enhanced imports. +from textual_enhanced.widgets import EnhancedOptionList + ############################################################################## # Local imports. from ..data import PEP, PEPStatus, PEPType, PostHistory @@ -37,7 +41,6 @@ ShowType, VisitPEP, ) -from .extended_option_list import OptionListEx ############################################################################## @@ -141,7 +144,7 @@ def show(self, text: str) -> None: class Item(Option): """Type of an item that goes in a list.""" - def select(self, parent: OptionListEx) -> None: + def select(self, parent: EnhancedOptionList) -> None: """Perform the selection action for the item. Args: @@ -163,7 +166,7 @@ def __init__(self, status: PEPStatus) -> None: """The PEP status to display.""" super().__init__(status) - def select(self, parent: OptionListEx) -> None: + def select(self, parent: EnhancedOptionList) -> None: """Perform the selection action for the item. Args: @@ -186,7 +189,7 @@ def __init__(self, pep_type: PEPType) -> None: """The PEP type to display.""" super().__init__(pep_type) - def select(self, parent: OptionListEx) -> None: + def select(self, parent: EnhancedOptionList) -> None: """Perform the selection action for the item. Args: @@ -209,7 +212,7 @@ def __init__(self, pep: int) -> None: """The PEP number to display.""" super().__init__(f"PEP{pep}") - def select(self, parent: OptionListEx) -> None: + def select(self, parent: EnhancedOptionList) -> None: """Perform the selection action for the item. Args: @@ -232,7 +235,7 @@ def __init__(self, author: str) -> None: """The author to display.""" super().__init__(author) - def select(self, parent: OptionListEx) -> None: + def select(self, parent: EnhancedOptionList) -> None: """Perform the selection action for the item. Args: @@ -255,7 +258,7 @@ def __init__(self, python_version: str) -> None: """The Python version to display.""" super().__init__(python_version) - def select(self, parent: OptionListEx) -> None: + def select(self, parent: EnhancedOptionList) -> None: """Perform the selection action for the item. Args: @@ -279,7 +282,7 @@ def __init__(self, url: str, title: str | None = None) -> None: """The URL to display.""" super().__init__(title or url) - def select(self, parent: OptionListEx) -> None: + def select(self, parent: EnhancedOptionList) -> None: """Perform the selection action for the item. Args: @@ -312,7 +315,7 @@ def __init__(self, post: PostHistory) -> None: title = post.url super().__init__(title) - def select(self, parent: OptionListEx) -> None: + def select(self, parent: EnhancedOptionList) -> None: """Perform the selection action for the item. Args: @@ -325,7 +328,7 @@ def select(self, parent: OptionListEx) -> None: ############################################################################## -class ClickableValue(OptionListEx): +class ClickableValue(EnhancedOptionList): """Show a value that the user can interact with.""" DEFAULT_CSS = """ @@ -398,8 +401,8 @@ def on_blur(self) -> None: self._highlighted_memory = self.highlighted self.highlighted = None - @on(OptionListEx.OptionSelected) - def select(self, message: OptionListEx.OptionSelected) -> None: + @on(EnhancedOptionList.OptionSelected) + def select(self, message: EnhancedOptionList.OptionSelected) -> None: assert isinstance(message.option, Item) message.option.select(self) diff --git a/src/peplum/app/widgets/peps_view.py b/src/peplum/app/widgets/peps_view.py index 1bb60ee..0bcb564 100644 --- a/src/peplum/app/widgets/peps_view.py +++ b/src/peplum/app/widgets/peps_view.py @@ -19,11 +19,14 @@ from textual.reactive import var from textual.widgets.option_list import Option +############################################################################## +# Textual enhanced imports. +from textual_enhanced.widgets import EnhancedOptionList + ############################################################################## # Local imports. from ..data import PEP, PEPs from ..messages import VisitPEP -from .extended_option_list import OptionListEx ############################################################################## @@ -80,7 +83,7 @@ def pep(self) -> PEP: ############################################################################## -class PEPsView(OptionListEx): +class PEPsView(EnhancedOptionList): """A widget for viewing a list of PEPs.""" HELP = """ @@ -109,15 +112,15 @@ class PEPHighlighted(Message): pep: PEP """The highlighted PEP.""" - @on(OptionListEx.OptionHighlighted) - def select_pep(self, message: OptionListEx.OptionSelected) -> None: + @on(EnhancedOptionList.OptionHighlighted) + def select_pep(self, message: EnhancedOptionList.OptionSelected) -> None: """Send a message to say a particular PEP has been selected.""" message.stop() assert isinstance(message.option, PEPView) self.post_message(self.PEPHighlighted(message.option.pep)) - @on(OptionListEx.OptionSelected) - def visit_pep(self, message: OptionListEx.OptionSelected) -> None: + @on(EnhancedOptionList.OptionSelected) + def visit_pep(self, message: EnhancedOptionList.OptionSelected) -> None: """Send a message to say the user wants to visit a PEP's webpage.""" message.stop() assert isinstance(message.option, PEPView) diff --git a/src/peplum/app/widgets/text_viewer.py b/src/peplum/app/widgets/text_viewer.py deleted file mode 100644 index 1bc3ff2..0000000 --- a/src/peplum/app/widgets/text_viewer.py +++ /dev/null @@ -1,82 +0,0 @@ -"""A widget for viewing text.""" - -############################################################################## -# Textual imports. -from textual.widgets import TextArea - - -############################################################################## -class TextViewer(TextArea): - """A widget for viewing text.""" - - DEFAULT_CSS = """ - TextViewer { - background: transparent; - border: none; - &:focus { - border: none; - } - & > .text-area--cursor-line { - background: transparent; - } - } - """ - - BINDINGS = [ - ("<", "cursor_line_start"), - (">", "cursor_line_end"), - ("c, C", "copy"), - ] - - def __init__( - self, - text: str = "", - *, - name: str | None = None, - id: str | None = None, - classes: str | None = None, - disabled: bool = False, - ) -> None: - """Initialise the object. - - Args: - text: The text to view. - name: The name of the TextViewer. - id: The ID of the TextViewer in the DOM. - classes: The CSS classes of the TextViewer. - disabled: Whether the TextViewer is disabled or not. - """ - super().__init__( - text, - read_only=True, - name=name, - id=id, - classes=classes, - disabled=disabled, - ) - - def action_copy(self) -> None: - """Action for copying text to the clipboard.""" - if for_clipboard := self.selected_text: - self.notify("Selected text copied to the clipboard.") - else: - for_clipboard = self.text - self.notify("All text copied to the clipboard.") - self.app.copy_to_clipboard(for_clipboard) - - def action_cursor_line_start(self, select: bool = False) -> None: - """Add a slightly smarter use of going 'home'.""" - if self.cursor_at_start_of_line: - self.move_cursor(self.document.start, select=select) - else: - super().action_cursor_line_start(select) - - def action_cursor_line_end(self, select: bool = False) -> None: - """Add a slightly smarter use of going 'end'.""" - if self.cursor_at_end_of_line: - self.move_cursor(self.document.end, select=select) - else: - super().action_cursor_line_end(select) - - -### text_viewer.py ends here