1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
|
from typing import Any
from typing import Callable
from typing import Dict
from typing import Iterable
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union
from prompt_toolkit.completion import CompleteEvent
from prompt_toolkit.completion import Completer
from prompt_toolkit.completion import Completion
from prompt_toolkit.document import Document
from prompt_toolkit.formatted_text import HTML
from prompt_toolkit.lexers import SimpleLexer
from prompt_toolkit.shortcuts.prompt import CompleteStyle
from prompt_toolkit.shortcuts.prompt import PromptSession
from prompt_toolkit.styles import Style
from questionary.constants import DEFAULT_QUESTION_PREFIX
from questionary.prompts.common import build_validator
from questionary.question import Question
from questionary.styles import merge_styles_default
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)], # noqa: E203
choice[index + len(word_before_cursor) : len(choice)], # noqa: E203
)
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",
)
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 species',
... choices=[
... 'Camponotus pennsylvanicus',
... 'Linepithema humile',
... 'Eciton burchellii',
... "Atta colombica",
... 'Polyergus lucidus',
... 'Polyergus rufescens',
... ]).ask()
? Choose ant species 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])
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 = 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)
|