Source code for questionary.prompts.autocomplete

from typing import (
    Any,
    Callable,
    Dict,
    List,
    Optional,
    Tuple,
    Union,
    Iterable,
)

from prompt_toolkit.completion import CompleteEvent, Completer, Completion
from prompt_toolkit.document import Document
from prompt_toolkit.formatted_text import HTML
from prompt_toolkit.shortcuts.prompt import PromptSession, CompleteStyle
from prompt_toolkit.styles import Style, merge_styles
from prompt_toolkit.lexers import SimpleLexer

from questionary.constants import DEFAULT_QUESTION_PREFIX, DEFAULT_STYLE
from questionary.prompts.common import build_validator
from questionary.question import Question


class WordCompleter(Completer):
    choices_source: Union[List[str], Callable[[], List[str]]]
    ignore_case: bool
    meta_information: Dict[str, Any]
    match_middle: bool

    def __init__(
        self,
        choices: Union[List[str], Callable[[], List[str]]],
        ignore_case: bool = True,
        meta_information: Optional[Dict[str, Any]] = None,
        match_middle: bool = True,
    ) -> None:
        self.choices_source = choices
        self.ignore_case = ignore_case
        self.meta_information = meta_information or {}
        self.match_middle = match_middle

    def _choices(self) -> Iterable[str]:
        return (
            self.choices_source()
            if callable(self.choices_source)
            else self.choices_source
        )

    def _choice_matches(self, word_before_cursor: str, choice: str) -> int:
        """Match index if found, -1 if not. """

        if self.ignore_case:
            choice = choice.lower()

        if self.match_middle:
            return choice.find(word_before_cursor)
        elif choice.startswith(word_before_cursor):
            return 0
        else:
            return -1

    @staticmethod
    def _display_for_choice(choice: str, index: int, word_before_cursor: str) -> HTML:
        return HTML("{}<b><u>{}</u></b>{}").format(
            choice[:index],
            choice[index : index + len(word_before_cursor)],
            choice[index + len(word_before_cursor) : len(choice)],
        )

    def get_completions(
        self, document: Document, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        choices = self._choices()

        # Get word/text before cursor.
        word_before_cursor = document.text_before_cursor

        if self.ignore_case:
            word_before_cursor = word_before_cursor.lower()

        for choice in choices:
            index = self._choice_matches(word_before_cursor, choice)
            if index == -1:
                # didn't find a match
                continue

            display_meta = self.meta_information.get(choice, "")
            display = self._display_for_choice(choice, index, word_before_cursor)

            yield Completion(
                choice,
                start_position=-len(choice),
                display=display.formatted_text,
                display_meta=display_meta,
                style="class:answer",
                selected_style="class:selected",
            )


[docs]def autocomplete( message: str, choices: List[str], default: str = "", qmark: str = DEFAULT_QUESTION_PREFIX, completer: Optional[Completer] = None, meta_information: Optional[Dict[str, Any]] = None, ignore_case: bool = True, match_middle: bool = True, complete_style: CompleteStyle = CompleteStyle.COLUMN, validate: Any = None, style: Optional[Style] = None, **kwargs: Any, ) -> Question: """Prompt the user to enter a message with autocomplete help. Example: >>> import questionary >>> questionary.autocomplete( ... 'Choose ant specie', ... choices=[ ... 'Camponotus pennsylvanicus', ... 'Linepithema humile', ... 'Eciton burchellii', ... "Atta colombica", ... 'Polyergus lucidus', ... 'Polyergus rufescens', ... ]).ask() ? Choose ant specie Atta colombica 'Atta colombica' .. image:: ../images/autocomplete.gif This is just a really basic example, the prompt can be customised using the parameters. Args: message: Question text choices: Items shown in the selection, this contains items as strings default: Default return value (single value). qmark: Question prefix displayed in front of the question. By default this is a ``?`` completer: A prompt_toolkit :class:`prompt_toolkit.completion.Completion` implementation. If not set, a questionary completer implementation will be used. meta_information: A dictionary with information/anything about choices. ignore_case: If true autocomplete would ignore case. match_middle: If true autocomplete would search in every string position not only in string begin. complete_style: How autocomplete menu would be shown, it could be ``COLUMN`` ``MULTI_COLUMN`` or ``READLINE_LIKE`` from :class:`prompt_toolkit.shortcuts.CompleteStyle`. validate: Require the entered value to pass a validation. The value can not be submitted until the validator accepts it (e.g. to check minimum password length). This can either be a function accepting the input and returning a boolean, or an class reference to a subclass of the prompt toolkit Validator class. style: A custom color and style for the question parts. You can configure colors as well as font types for different elements. Returns: :class:`Question`: Question instance, ready to be prompted (using ``.ask()``). """ merged_style = merge_styles([DEFAULT_STYLE, style]) def get_prompt_tokens() -> List[Tuple[str, str]]: return [("class:qmark", qmark), ("class:question", " {} ".format(message))] def get_meta_style(meta: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]: if meta: for key in meta: meta[key] = HTML("<text>{}</text>").format(meta[key]) return meta validator = build_validator(validate) if completer is None: if not choices: raise ValueError("No choices is given, you should use Text question.") # use the default completer completer = WordCompleter( choices, ignore_case=ignore_case, meta_information=get_meta_style(meta_information), match_middle=match_middle, ) p = PromptSession( get_prompt_tokens, lexer=SimpleLexer("class:answer"), style=merged_style, completer=completer, validator=validator, complete_style=complete_style, **kwargs, ) p.default_buffer.reset(Document(default)) return Question(p.app)