mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Use workers to get suggestions.
This commit is contained in:
@@ -1,35 +1,43 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Generic, Iterable, Optional, TypeVar
|
from typing import Iterable
|
||||||
|
|
||||||
from ._types import MessageTarget
|
from .dom import DOMNode
|
||||||
from .message import Message
|
from .message import Message
|
||||||
|
|
||||||
_SuggestionRequester = TypeVar("_SuggestionRequester", bound=MessageTarget)
|
|
||||||
"""Type variable for the message target that will request suggestions."""
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SuggestionReady(Message):
|
class SuggestionReady(Message):
|
||||||
"""Sent when a completion suggestion is ready."""
|
"""Sent when a completion suggestion is ready."""
|
||||||
|
|
||||||
input_value: str
|
initial_value: str
|
||||||
"""The input value that the suggestion was for."""
|
"""The value to which the suggestion is for."""
|
||||||
suggestion: str
|
suggestion: str
|
||||||
"""The string suggestion."""
|
"""The string suggestion."""
|
||||||
|
|
||||||
|
|
||||||
class Suggester(ABC, Generic[_SuggestionRequester]):
|
class Suggester(ABC):
|
||||||
"""Defines how [inputs][textual.widgets.Input] generate completion suggestions.
|
"""Defines how widgets generate completion suggestions.
|
||||||
|
|
||||||
To define a custom suggester, subclass `Suggester` and implement the async method
|
To define a custom suggester, subclass `Suggester` and implement the async method
|
||||||
`get_suggestion`.
|
`get_suggestion`.
|
||||||
See [`SuggestFromList`][textual.suggester.SuggestFromList] for an example.
|
See [`SuggestFromList`][textual.suggester.SuggestFromList] for an example.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def _get_suggestion(
|
cache: dict[str, str | None] | None
|
||||||
self, requester: _SuggestionRequester, value: str
|
"""Suggestion cache, if used."""
|
||||||
) -> None:
|
|
||||||
|
def __init__(self, use_cache: bool = True):
|
||||||
|
"""Create a suggester object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
use_cache: Whether to cache suggestion results.
|
||||||
|
"""
|
||||||
|
self.cache = {} if use_cache else None
|
||||||
|
|
||||||
|
async def _get_suggestion(self, requester: DOMNode, value: str) -> None:
|
||||||
"""Used by widgets to get completion suggestions.
|
"""Used by widgets to get completion suggestions.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
@@ -40,20 +48,29 @@ class Suggester(ABC, Generic[_SuggestionRequester]):
|
|||||||
requester: The message target that requested a suggestion.
|
requester: The message target that requested a suggestion.
|
||||||
value: The current input value to complete.
|
value: The current input value to complete.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if self.cache is None or value not in self.cache:
|
||||||
suggestion = await self.get_suggestion(value)
|
suggestion = await self.get_suggestion(value)
|
||||||
|
if self.cache is not None:
|
||||||
|
self.cache[value] = suggestion
|
||||||
|
else:
|
||||||
|
suggestion = self.cache[value]
|
||||||
|
|
||||||
if suggestion is None:
|
if suggestion is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
requester.post_message(SuggestionReady(value, suggestion))
|
requester.post_message(SuggestionReady(value, suggestion))
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def get_suggestion(self, value: str) -> Optional[str]:
|
async def get_suggestion(self, value: str) -> str | None:
|
||||||
"""Try to get a completion suggestion for the given input value.
|
"""Try to get a completion suggestion for the given input value.
|
||||||
|
|
||||||
Custom suggesters should implement this method.
|
Custom suggesters should implement this method.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
If your implementation is not deterministic, you may need to disable caching.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
value: The current value of the input widget.
|
value: The current value of the requester widget.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A valid suggestion or `None`.
|
A valid suggestion or `None`.
|
||||||
@@ -61,8 +78,18 @@ class Suggester(ABC, Generic[_SuggestionRequester]):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SuggestFromList(Suggester[_SuggestionRequester]):
|
class SuggestFromList(Suggester):
|
||||||
"""Give completion suggestions based on a fixed list of options."""
|
"""Give completion suggestions based on a fixed list of options.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```py
|
||||||
|
countries = ["England", "Scotland", "Portugal", "Spain", "France"]
|
||||||
|
|
||||||
|
class MyApp(App[None]):
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
yield Input(suggester=SuggestFromList(countries))
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, suggestions: Iterable[str]) -> None:
|
def __init__(self, suggestions: Iterable[str]) -> None:
|
||||||
"""Creates a suggester based off of a given iterable of possibilities.
|
"""Creates a suggester based off of a given iterable of possibilities.
|
||||||
@@ -70,9 +97,10 @@ class SuggestFromList(Suggester[_SuggestionRequester]):
|
|||||||
Args:
|
Args:
|
||||||
suggestions: Valid suggestions sorted by decreasing priority.
|
suggestions: Valid suggestions sorted by decreasing priority.
|
||||||
"""
|
"""
|
||||||
self.suggestions = list(suggestions)
|
super().__init__()
|
||||||
|
self._suggestions = list(suggestions)
|
||||||
|
|
||||||
async def get_suggestion(self, value: str) -> Optional[str]:
|
async def get_suggestion(self, value: str) -> str | None:
|
||||||
"""Gets a completion from the given possibilities.
|
"""Gets a completion from the given possibilities.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -81,7 +109,7 @@ class SuggestFromList(Suggester[_SuggestionRequester]):
|
|||||||
Returns:
|
Returns:
|
||||||
A valid completion suggestion or `None`.
|
A valid completion suggestion or `None`.
|
||||||
"""
|
"""
|
||||||
for suggestion in self.suggestions:
|
for suggestion in self._suggestions:
|
||||||
if suggestion.startswith(value):
|
if suggestion.startswith(value):
|
||||||
return suggestion
|
return suggestion
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ class Input(Widget, can_focus=True):
|
|||||||
_cursor_visible = reactive(True)
|
_cursor_visible = reactive(True)
|
||||||
password = reactive(False)
|
password = reactive(False)
|
||||||
max_size: reactive[int | None] = reactive(None)
|
max_size: reactive[int | None] = reactive(None)
|
||||||
suggester: Suggester[Input] | None
|
suggester: Suggester | None
|
||||||
"""The suggester used to provide completions as the user types."""
|
"""The suggester used to provide completions as the user types."""
|
||||||
_suggestion = reactive("")
|
_suggestion = reactive("")
|
||||||
"""A completion suggestion for the current value in the input."""
|
"""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:
|
async def watch_value(self, value: str) -> None:
|
||||||
self._suggestion = ""
|
self._suggestion = ""
|
||||||
if self.suggester and value:
|
if self.suggester and value:
|
||||||
self.call_next(self.suggester._get_suggestion, self, value)
|
self.run_worker(self.suggester._get_suggestion(self, value))
|
||||||
if self.styles.auto_dimensions:
|
if self.styles.auto_dimensions:
|
||||||
self.refresh(layout=True)
|
self.refresh(layout=True)
|
||||||
self.post_message(self.Changed(self, value))
|
self.post_message(self.Changed(self, value))
|
||||||
@@ -388,7 +388,7 @@ class Input(Widget, can_focus=True):
|
|||||||
|
|
||||||
async def _on_suggestion_ready(self, event: SuggestionReady) -> None:
|
async def _on_suggestion_ready(self, event: SuggestionReady) -> None:
|
||||||
"""Handle suggestion messages and set the suggestion when relevant."""
|
"""Handle suggestion messages and set the suggestion when relevant."""
|
||||||
if event.input_value == self.value:
|
if event.initial_value == self.value:
|
||||||
self._suggestion = event.suggestion
|
self._suggestion = event.suggestion
|
||||||
|
|
||||||
def insert_text_at_cursor(self, text: str) -> None:
|
def insert_text_at_cursor(self, text: str) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user