mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Update suggester implementation.
This commit is contained in:
@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterable
|
||||
|
||||
from ._cache import FIFOCache
|
||||
from ._cache import LRUCache
|
||||
from .dom import DOMNode
|
||||
from .message import Message
|
||||
|
||||
@@ -13,7 +13,7 @@ from .message import Message
|
||||
class SuggestionReady(Message):
|
||||
"""Sent when a completion suggestion is ready."""
|
||||
|
||||
initial_value: str
|
||||
value: str
|
||||
"""The value to which the suggestion is for."""
|
||||
suggestion: str
|
||||
"""The string suggestion."""
|
||||
@@ -27,16 +27,20 @@ class Suggester(ABC):
|
||||
See [`SuggestFromList`][textual.suggester.SuggestFromList] for an example.
|
||||
"""
|
||||
|
||||
cache: FIFOCache[str, str | None] | None
|
||||
cache: LRUCache[str, str | None] | None
|
||||
"""Suggestion cache, if used."""
|
||||
|
||||
def __init__(self, use_cache: bool = True) -> None:
|
||||
def __init__(self, *, use_cache: bool = True, case_sensitive: bool = True) -> None:
|
||||
"""Create a suggester object.
|
||||
|
||||
Args:
|
||||
use_cache: Whether to cache suggestion results.
|
||||
case_sensitive: Whether suggestions are case sensitive or not.
|
||||
If they are not, incoming values are casefolded before generating
|
||||
the suggestion.
|
||||
"""
|
||||
self.cache = FIFOCache(1024) if use_cache else None
|
||||
self.cache = LRUCache(1024) if use_cache else None
|
||||
self.case_sensitive = case_sensitive
|
||||
|
||||
async def _get_suggestion(self, requester: DOMNode, value: str) -> None:
|
||||
"""Used by widgets to get completion suggestions.
|
||||
@@ -47,15 +51,16 @@ class Suggester(ABC):
|
||||
|
||||
Args:
|
||||
requester: The message target that requested a suggestion.
|
||||
value: The current input value to complete.
|
||||
value: The current value to complete.
|
||||
"""
|
||||
|
||||
if self.cache is None or value not in self.cache:
|
||||
suggestion = await self.get_suggestion(value)
|
||||
normalized_value = value if self.case_sensitive else value.casefold()
|
||||
if self.cache is None or normalized_value not in self.cache:
|
||||
suggestion = await self.get_suggestion(normalized_value)
|
||||
if self.cache is not None:
|
||||
self.cache[value] = suggestion
|
||||
self.cache[normalized_value] = suggestion
|
||||
else:
|
||||
suggestion = self.cache[value]
|
||||
suggestion = self.cache[normalized_value]
|
||||
|
||||
if suggestion is None:
|
||||
return
|
||||
@@ -67,6 +72,9 @@ class Suggester(ABC):
|
||||
|
||||
Custom suggesters should implement this method.
|
||||
|
||||
Note:
|
||||
The value argument will be casefolded if `self.case_sensitive` is `False`.
|
||||
|
||||
Note:
|
||||
If your implementation is not deterministic, you may need to disable caching.
|
||||
|
||||
@@ -88,18 +96,32 @@ class SuggestFromList(Suggester):
|
||||
|
||||
class MyApp(App[None]):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Input(suggester=SuggestFromList(countries))
|
||||
yield Input(suggester=SuggestFromList(countries, case_sensitive=False))
|
||||
```
|
||||
|
||||
If the user types ++p++ inside the input widget, a completion suggestion
|
||||
for `"Portugal"` appears.
|
||||
"""
|
||||
|
||||
def __init__(self, suggestions: Iterable[str]) -> None:
|
||||
def __init__(
|
||||
self, suggestions: Iterable[str], *, case_sensitive: bool = True
|
||||
) -> None:
|
||||
"""Creates a suggester based off of a given iterable of possibilities.
|
||||
|
||||
Args:
|
||||
suggestions: Valid suggestions sorted by decreasing priority.
|
||||
case_sensitive: Whether suggestions are computed in a case sensitive manner
|
||||
or not. The values provided in the argument `suggestions` represent the
|
||||
canonical representation of the completions and they will be suggested
|
||||
with that same casing.
|
||||
"""
|
||||
super().__init__()
|
||||
super().__init__(case_sensitive=case_sensitive)
|
||||
self._suggestions = list(suggestions)
|
||||
self._for_comparison = (
|
||||
self._suggestions
|
||||
if self.case_sensitive
|
||||
else [suggestion.casefold() for suggestion in self._suggestions]
|
||||
)
|
||||
|
||||
async def get_suggestion(self, value: str) -> str | None:
|
||||
"""Gets a completion from the given possibilities.
|
||||
@@ -110,7 +132,7 @@ class SuggestFromList(Suggester):
|
||||
Returns:
|
||||
A valid completion suggestion or `None`.
|
||||
"""
|
||||
for suggestion in self._suggestions:
|
||||
for idx, suggestion in enumerate(self._for_comparison):
|
||||
if suggestion.startswith(value):
|
||||
return suggestion
|
||||
return self._suggestions[idx]
|
||||
return None
|
||||
|
||||
@@ -38,9 +38,7 @@ class _InputRenderable:
|
||||
value = input.value
|
||||
value_length = len(value)
|
||||
suggestion = input._suggestion
|
||||
show_suggestion = suggestion.startswith(value) and (
|
||||
len(suggestion) > value_length
|
||||
)
|
||||
show_suggestion = len(suggestion) > value_length
|
||||
if show_suggestion:
|
||||
result += Text(
|
||||
suggestion[value_length:],
|
||||
@@ -388,7 +386,7 @@ class Input(Widget, can_focus=True):
|
||||
|
||||
async def _on_suggestion_ready(self, event: SuggestionReady) -> None:
|
||||
"""Handle suggestion messages and set the suggestion when relevant."""
|
||||
if event.initial_value == self.value:
|
||||
if event.value == self.value:
|
||||
self._suggestion = event.suggestion
|
||||
|
||||
def insert_text_at_cursor(self, text: str) -> None:
|
||||
|
||||
Reference in New Issue
Block a user