You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			178 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			178 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
`Fish-style <http://fishshell.com/>`_  like auto-suggestion.
 | 
						|
 | 
						|
While a user types input in a certain buffer, suggestions are generated
 | 
						|
(asynchronously.) Usually, they are displayed after the input. When the cursor
 | 
						|
presses the right arrow and the cursor is at the end of the input, the
 | 
						|
suggestion will be inserted.
 | 
						|
 | 
						|
If you want the auto suggestions to be asynchronous (in a background thread),
 | 
						|
because they take too much time, and could potentially block the event loop,
 | 
						|
then wrap the :class:`.AutoSuggest` instance into a
 | 
						|
:class:`.ThreadedAutoSuggest`.
 | 
						|
"""
 | 
						|
 | 
						|
from __future__ import annotations
 | 
						|
 | 
						|
from abc import ABCMeta, abstractmethod
 | 
						|
from typing import TYPE_CHECKING, Callable
 | 
						|
 | 
						|
from prompt_toolkit.eventloop import run_in_executor_with_context
 | 
						|
 | 
						|
from .document import Document
 | 
						|
from .filters import Filter, to_filter
 | 
						|
 | 
						|
if TYPE_CHECKING:
 | 
						|
    from .buffer import Buffer
 | 
						|
 | 
						|
__all__ = [
 | 
						|
    "Suggestion",
 | 
						|
    "AutoSuggest",
 | 
						|
    "ThreadedAutoSuggest",
 | 
						|
    "DummyAutoSuggest",
 | 
						|
    "AutoSuggestFromHistory",
 | 
						|
    "ConditionalAutoSuggest",
 | 
						|
    "DynamicAutoSuggest",
 | 
						|
]
 | 
						|
 | 
						|
 | 
						|
class Suggestion:
 | 
						|
    """
 | 
						|
    Suggestion returned by an auto-suggest algorithm.
 | 
						|
 | 
						|
    :param text: The suggestion text.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, text: str) -> None:
 | 
						|
        self.text = text
 | 
						|
 | 
						|
    def __repr__(self) -> str:
 | 
						|
        return f"Suggestion({self.text})"
 | 
						|
 | 
						|
 | 
						|
class AutoSuggest(metaclass=ABCMeta):
 | 
						|
    """
 | 
						|
    Base class for auto suggestion implementations.
 | 
						|
    """
 | 
						|
 | 
						|
    @abstractmethod
 | 
						|
    def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion | None:
 | 
						|
        """
 | 
						|
        Return `None` or a :class:`.Suggestion` instance.
 | 
						|
 | 
						|
        We receive both :class:`~prompt_toolkit.buffer.Buffer` and
 | 
						|
        :class:`~prompt_toolkit.document.Document`. The reason is that auto
 | 
						|
        suggestions are retrieved asynchronously. (Like completions.) The
 | 
						|
        buffer text could be changed in the meantime, but ``document`` contains
 | 
						|
        the buffer document like it was at the start of the auto suggestion
 | 
						|
        call. So, from here, don't access ``buffer.text``, but use
 | 
						|
        ``document.text`` instead.
 | 
						|
 | 
						|
        :param buffer: The :class:`~prompt_toolkit.buffer.Buffer` instance.
 | 
						|
        :param document: The :class:`~prompt_toolkit.document.Document` instance.
 | 
						|
        """
 | 
						|
 | 
						|
    async def get_suggestion_async(
 | 
						|
        self, buff: Buffer, document: Document
 | 
						|
    ) -> Suggestion | None:
 | 
						|
        """
 | 
						|
        Return a :class:`.Future` which is set when the suggestions are ready.
 | 
						|
        This function can be overloaded in order to provide an asynchronous
 | 
						|
        implementation.
 | 
						|
        """
 | 
						|
        return self.get_suggestion(buff, document)
 | 
						|
 | 
						|
 | 
						|
class ThreadedAutoSuggest(AutoSuggest):
 | 
						|
    """
 | 
						|
    Wrapper that runs auto suggestions in a thread.
 | 
						|
    (Use this to prevent the user interface from becoming unresponsive if the
 | 
						|
    generation of suggestions takes too much time.)
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, auto_suggest: AutoSuggest) -> None:
 | 
						|
        self.auto_suggest = auto_suggest
 | 
						|
 | 
						|
    def get_suggestion(self, buff: Buffer, document: Document) -> Suggestion | None:
 | 
						|
        return self.auto_suggest.get_suggestion(buff, document)
 | 
						|
 | 
						|
    async def get_suggestion_async(
 | 
						|
        self, buff: Buffer, document: Document
 | 
						|
    ) -> Suggestion | None:
 | 
						|
        """
 | 
						|
        Run the `get_suggestion` function in a thread.
 | 
						|
        """
 | 
						|
 | 
						|
        def run_get_suggestion_thread() -> Suggestion | None:
 | 
						|
            return self.get_suggestion(buff, document)
 | 
						|
 | 
						|
        return await run_in_executor_with_context(run_get_suggestion_thread)
 | 
						|
 | 
						|
 | 
						|
class DummyAutoSuggest(AutoSuggest):
 | 
						|
    """
 | 
						|
    AutoSuggest class that doesn't return any suggestion.
 | 
						|
    """
 | 
						|
 | 
						|
    def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion | None:
 | 
						|
        return None  # No suggestion
 | 
						|
 | 
						|
 | 
						|
class AutoSuggestFromHistory(AutoSuggest):
 | 
						|
    """
 | 
						|
    Give suggestions based on the lines in the history.
 | 
						|
    """
 | 
						|
 | 
						|
    def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion | None:
 | 
						|
        history = buffer.history
 | 
						|
 | 
						|
        # Consider only the last line for the suggestion.
 | 
						|
        text = document.text.rsplit("\n", 1)[-1]
 | 
						|
 | 
						|
        # Only create a suggestion when this is not an empty line.
 | 
						|
        if text.strip():
 | 
						|
            # Find first matching line in history.
 | 
						|
            for string in reversed(list(history.get_strings())):
 | 
						|
                for line in reversed(string.splitlines()):
 | 
						|
                    if line.startswith(text):
 | 
						|
                        return Suggestion(line[len(text) :])
 | 
						|
 | 
						|
        return None
 | 
						|
 | 
						|
 | 
						|
class ConditionalAutoSuggest(AutoSuggest):
 | 
						|
    """
 | 
						|
    Auto suggest that can be turned on and of according to a certain condition.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, auto_suggest: AutoSuggest, filter: bool | Filter) -> None:
 | 
						|
        self.auto_suggest = auto_suggest
 | 
						|
        self.filter = to_filter(filter)
 | 
						|
 | 
						|
    def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion | None:
 | 
						|
        if self.filter():
 | 
						|
            return self.auto_suggest.get_suggestion(buffer, document)
 | 
						|
 | 
						|
        return None
 | 
						|
 | 
						|
 | 
						|
class DynamicAutoSuggest(AutoSuggest):
 | 
						|
    """
 | 
						|
    Validator class that can dynamically returns any Validator.
 | 
						|
 | 
						|
    :param get_validator: Callable that returns a :class:`.Validator` instance.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, get_auto_suggest: Callable[[], AutoSuggest | None]) -> None:
 | 
						|
        self.get_auto_suggest = get_auto_suggest
 | 
						|
 | 
						|
    def get_suggestion(self, buff: Buffer, document: Document) -> Suggestion | None:
 | 
						|
        auto_suggest = self.get_auto_suggest() or DummyAutoSuggest()
 | 
						|
        return auto_suggest.get_suggestion(buff, document)
 | 
						|
 | 
						|
    async def get_suggestion_async(
 | 
						|
        self, buff: Buffer, document: Document
 | 
						|
    ) -> Suggestion | None:
 | 
						|
        auto_suggest = self.get_auto_suggest() or DummyAutoSuggest()
 | 
						|
        return await auto_suggest.get_suggestion_async(buff, document)
 |