diff --git a/CHANGELOG.md b/CHANGELOG.md index bef138a5d..ba5bec3d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,14 +10,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added -- `Suggester` API to compose with `Input` for automatic input suggestions while typing https://github.com/Textualize/textual/issues/2330 -- `SuggestFromList` class to let `Input` widgets get completions from a fixed set of options https://github.com/Textualize/textual/pull/2604 -- `Input` has new component class `input--suggestion` https://github.com/Textualize/textual/pull/2604 +- `Suggester` API to compose with widgets for automatic suggestions https://github.com/Textualize/textual/issues/2330 +- `SuggestFromList` class to let widgets get completions from a fixed set of options https://github.com/Textualize/textual/pull/2604 +- `Input` has a new component class `input--suggestion` https://github.com/Textualize/textual/pull/2604 ### Changed - Keybinding right in `Input` is also used to accept a suggestion if the cursor is at the end of the input https://github.com/Textualize/textual/pull/2604 -- `Input.__init__` now accepts a suggester for completion suggestions https://github.com/Textualize/textual/pull/2604 +- `Input.__init__` now accepts a `suggester` attribute for completion suggestions https://github.com/Textualize/textual/pull/2604 ## [0.25.0] - 2023-05-17 diff --git a/src/textual/suggester.py b/src/textual/suggester.py index 888ce7cee..8fe0da029 100644 --- a/src/textual/suggester.py +++ b/src/textual/suggester.py @@ -1,11 +1,12 @@ from abc import ABC, abstractmethod from dataclasses import dataclass -from typing import TYPE_CHECKING, Iterable, Optional +from typing import Generic, Iterable, Optional, TypeVar +from ._types import MessageTarget from .message import Message -if TYPE_CHECKING: - from .widgets import Input +_SuggestionRequester = TypeVar("_SuggestionRequester", bound=MessageTarget) +"""Type variable for the message target that will request suggestions.""" @dataclass @@ -18,7 +19,7 @@ class SuggestionReady(Message): """The string suggestion.""" -class Suggester(ABC): +class Suggester(ABC, Generic[_SuggestionRequester]): """Defines how [inputs][textual.widgets.Input] generate completion suggestions. To define a custom suggester, subclass `Suggester` and implement the async method @@ -26,22 +27,24 @@ class Suggester(ABC): See [`SuggestFromList`][textual.suggester.SuggestFromList] for an example. """ - async def get(self, input: "Input", value: str) -> None: - """Used by [`Input`][textual.widgets.Input] to get completion suggestions. + async def _get_suggestion( + self, requester: _SuggestionRequester, value: str + ) -> None: + """Used by widgets to get completion suggestions. Note: When implementing custom suggesters, this method does not need to be overridden. Args: - input: The input widget that requested a suggestion. + requester: The message target that requested a suggestion. value: The current input value to complete. """ suggestion = await self.get_suggestion(value) if suggestion is None: return - input.post_message(SuggestionReady(value, suggestion)) + requester.post_message(SuggestionReady(value, suggestion)) @abstractmethod async def get_suggestion(self, value: str) -> Optional[str]: @@ -55,10 +58,10 @@ class Suggester(ABC): Returns: A valid suggestion or `None`. """ - raise NotImplementedError() + pass -class SuggestFromList(Suggester): +class SuggestFromList(Suggester[_SuggestionRequester]): """Give completion suggestions based on a fixed list of options.""" def __init__(self, suggestions: Iterable[str]) -> None: @@ -73,10 +76,10 @@ class SuggestFromList(Suggester): """Gets a completion from the given possibilities. Args: - value: The current value of the input widget. + value: The current value. Returns: - A valid suggestion or `None`. + A valid completion suggestion or `None`. """ for suggestion in self.suggestions: if suggestion.startswith(value): diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index 104b7c970..d87bb47da 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -156,7 +156,7 @@ class Input(Widget, can_focus=True): _cursor_visible = reactive(True) password = reactive(False) max_size: reactive[int | None] = reactive(None) - suggester: Suggester | None + suggester: Suggester[Input] | None """The suggester used to provide completions as the user types.""" _suggestion = reactive("") """A completion suggestion for the current value in the input.""" @@ -284,7 +284,7 @@ class Input(Widget, can_focus=True): async def watch_value(self, value: str) -> None: self._suggestion = "" if self.suggester and value: - self.call_next(self.suggester.get, self, value) + self.call_next(self.suggester._get_suggestion, self, value) if self.styles.auto_dimensions: self.refresh(layout=True) self.post_message(self.Changed(self, value))