Example #1
0
def test_blank_line_fix():
    def get_prompt_tokens():
        return [("class:question", "What is your favourite letter?")]

    ic = InquirerControl(["a", "b", "c"])

    inp = create_pipe_input()

    try:
        inp.send_text("")
        layout = common.create_inquirer_layout(ic,
                                               get_prompt_tokens,
                                               input=inp,
                                               output=DummyOutput())

        # usually this would be 2000000000000000000000000000000
        # but `common._fix_unecessary_blank_lines` makes sure
        # the main window is not as greedy (avoiding blank lines)
        assert (layout.container.preferred_height(
            100, 200).max == 1000000000000000000000000000001)
    finally:
        inp.close()
Example #2
0
def select(message: Text,
           choices: List[Union[Text, Choice, Dict[Text, Any]]],
           default: Optional[Text] = None,
           qmark: Text = DEFAULT_QUESTION_PREFIX,
           style: Optional[Style] = None,
           use_shortcuts: bool = False,
           use_indicator: bool = False,
           **kwargs: Any) -> Question:
    """Prompt the user to select one item from the list of choices.

    The user can only select one option.

    Args:
        message: Question text

        choices: Items shown in the selection, this can contain `Choice` or
                 or `Separator` objects or simple items as strings. Passing
                 `Choice` objects, allows you to configure the item more
                 (e.g. preselecting it or disabeling it).

        default: Default return value (single value).

        qmark: Question prefix displayed in front of the question.
               By default this is a `?`

        style: A custom color and style for the question parts. You can
               configure colors as well as font types for different elements.

        use_indicator: Flag to enable the small indicator in front of the
                       list highlighting the current location of the selection
                       cursor.

        use_shortcuts: Allow the user to select items from the list using
                       shortcuts. The shortcuts will be displayed in front of
                       the list items.
    Returns:
        Question: Question instance, ready to be prompted (using `.ask()`).
    """
    if choices is None or len(choices) == 0:
        raise ValueError('A list of choices needs to be provided.')

    if use_shortcuts and len(choices) > len(InquirerControl.SHORTCUT_KEYS):
        raise ValueError('A list with shortcuts supports a maximum of {} '
                         'choices as this is the maximum number '
                         'of keyboard shortcuts that are available. You'
                         'provided {} choices!'
                         ''.format(len(InquirerControl.SHORTCUT_KEYS),
                                   len(choices)))

    merged_style = merge_styles([DEFAULT_STYLE, style])

    ic = InquirerControl(choices,
                         default,
                         use_indicator=use_indicator,
                         use_shortcuts=use_shortcuts)

    def get_prompt_tokens():
        # noinspection PyListCreation
        tokens = [("class:qmark", qmark),
                  ("class:question", ' {} '.format(message))]

        if ic.is_answered:
            tokens.append(("class:answer", ' ' + ic.get_pointed_at().title))
        else:
            if use_shortcuts:
                tokens.append(("class:instruction", ' (Use shortcuts)'))
            else:
                tokens.append(("class:instruction", ' (Use arrow keys)'))

        return tokens

    layout = common.create_inquirer_layout(ic, get_prompt_tokens, **kwargs)

    bindings = KeyBindings()

    @bindings.add(Keys.ControlQ, eager=True)
    @bindings.add(Keys.ControlC, eager=True)
    def _(event):
        event.app.exit(exception=KeyboardInterrupt, style='class:aborting')

    if use_shortcuts:
        # add key bindings for choices
        for i, c in enumerate(ic.choices):
            if isinstance(c, Separator):
                continue

            # noinspection PyShadowingNames
            def _reg_binding(i, keys):
                # trick out late evaluation with a "function factory":
                # https://stackoverflow.com/a/3431699
                @bindings.add(keys, eager=True)
                def select_choice(event):
                    ic.pointed_at = i

            _reg_binding(i, c.shortcut_key)
    else:

        @bindings.add(Keys.Down, eager=True)
        @bindings.add("j", eager=True)
        def move_cursor_down(event):
            ic.select_next()
            while not ic.is_selection_valid():
                ic.select_next()

        @bindings.add(Keys.Up, eager=True)
        @bindings.add("k", eager=True)
        def move_cursor_up(event):
            ic.select_previous()
            while not ic.is_selection_valid():
                ic.select_previous()

    @bindings.add(Keys.ControlM, eager=True)
    def set_answer(event):
        ic.is_answered = True
        event.app.exit(result=ic.get_pointed_at().value)

    @bindings.add(Keys.Any)
    def other(event):
        """Disallow inserting other text. """
        pass

    return Question(
        Application(layout=layout,
                    key_bindings=bindings,
                    style=merged_style,
                    **kwargs))
Example #3
0
def checkbox(
    message: Text,
    choices: List[Union[Text, Choice, Dict[Text, Any]]],
    default: Optional[Text] = None,
    qmark: Text = DEFAULT_QUESTION_PREFIX,
    style: Optional[Style] = None,
    use_pointer: bool = True,
    **kwargs: Any
) -> Question:
    """Ask the user to select from a list of items.

    This is a multiselect, the user can choose one, none or many of the
    items.

    Args:
        message: Question text

        choices: Items shown in the selection, this can contain `Choice` or
                 or `Separator` objects or simple items as strings. Passing
                 `Choice` objects, allows you to configure the item more
                 (e.g. preselecting it or disabeling it).

        default: Default return value (single value). If you want to preselect
                 multiple items, use `Choice("foo", checked=True)` instead.

        qmark: Question prefix displayed in front of the question.
               By default this is a `?`

        style: A custom color and style for the question parts. You can
               configure colors as well as font types for different elements.

        use_pointer: Flag to enable the pointer in front of the currently
                     highlighted element.

    Returns:
        Question: Question instance, ready to be prompted (using `.ask()`).
    """

    merged_style = merge_styles([DEFAULT_STYLE, style])

    ic = InquirerControl(choices, default, use_pointer=use_pointer)

    def get_prompt_tokens():
        tokens = []

        tokens.append(("class:qmark", qmark))
        tokens.append(("class:question", " {} ".format(message)))
        if ic.is_answered:
            nbr_selected = len(ic.selected_options)
            if nbr_selected == 0:
                tokens.append(("class:answer", " done"))
            elif nbr_selected == 1:
                if isinstance(ic.get_selected_values()[0].title, list):
                    tokens.append(
                        (
                            "class:answer",
                            "".join(
                                [
                                    token[1]
                                    for token in ic.get_selected_values()[0].title
                                ]
                            ),
                        )
                    )
                else:
                    tokens.append(
                        (
                            "class:answer",
                            " [{}]".format(ic.get_selected_values()[0].title),
                        )
                    )
            else:
                tokens.append(
                    ("class:answer", " done ({} selections)".format(nbr_selected))
                )
        else:
            tokens.append(
                (
                    "class:instruction",
                    " (Use arrow keys to move, "
                    "<space> to select, "
                    "<a> to toggle, "
                    "<i> to invert)",
                )
            )
        return tokens

    layout = common.create_inquirer_layout(ic, get_prompt_tokens, **kwargs)

    bindings = KeyBindings()

    @bindings.add(Keys.ControlQ, eager=True)
    @bindings.add(Keys.ControlC, eager=True)
    def _(event):
        event.app.exit(exception=KeyboardInterrupt, style="class:aborting")

    @bindings.add(" ", eager=True)
    def toggle(event):
        pointed_choice = ic.get_pointed_at().value
        if pointed_choice in ic.selected_options:
            ic.selected_options.remove(pointed_choice)
        else:
            ic.selected_options.append(pointed_choice)

    @bindings.add("i", eager=True)
    def invert(event):
        inverted_selection = [
            c.value
            for c in ic.choices
            if not isinstance(c, Separator)
            and c.value not in ic.selected_options
            and not c.disabled
        ]
        ic.selected_options = inverted_selection

    @bindings.add("a", eager=True)
    def all(event):
        all_selected = True  # all choices have been selected
        for c in ic.choices:
            if (
                not isinstance(c, Separator)
                and c.value not in ic.selected_options
                and not c.disabled
            ):
                # add missing ones
                ic.selected_options.append(c.value)
                all_selected = False
        if all_selected:
            ic.selected_options = []

    @bindings.add(Keys.Down, eager=True)
    @bindings.add("j", eager=True)
    def move_cursor_down(event):
        ic.select_next()
        while not ic.is_selection_valid():
            ic.select_next()

    @bindings.add(Keys.Up, eager=True)
    @bindings.add("k", eager=True)
    def move_cursor_up(event):
        ic.select_previous()
        while not ic.is_selection_valid():
            ic.select_previous()

    @bindings.add(Keys.ControlM, eager=True)
    def set_answer(event):
        ic.is_answered = True
        event.app.exit(result=[c.value for c in ic.get_selected_values()])

    @bindings.add(Keys.Any)
    def other(event):
        """Disallow inserting other text. """
        pass

    return Question(
        Application(layout=layout, key_bindings=bindings, style=merged_style, **kwargs)
    )
Example #4
0
def select(
    message: str,
    choices: Sequence[Union[str, Choice, Dict[str, Any]]],
    default: Optional[Union[str, Choice, Dict[str, Any]]] = None,
    qmark: str = DEFAULT_QUESTION_PREFIX,
    style: Optional[Style] = None,
    use_shortcuts: bool = False,
    use_arrow_keys: bool = True,
    use_indicator: bool = False,
    use_pointer: bool = True,
    instruction: Optional[str] = None,
    **kwargs: Any,
) -> Question:
    """Prompt the user to select one item from the list of choices.

    The user can only select one option.

    Args:
        message: Question text

        choices: Items shown in the selection, this can contain `Choice` or
                 or `Separator` objects or simple items as strings. Passing
                 `Choice` objects, allows you to configure the item more
                 (e.g. preselecting it or disabeling it).

        default: A value corresponding to a selectable item in the choices,
                 to initially set the pointer position to.


        qmark: Question prefix displayed in front of the question.
               By default this is a `?`

        instruction: A hint on how to navigate the menu.
                     It's `(Use shortcuts)` if only `use_shortcuts` is set
                     to True, `(Use arrow keys or shortcuts)` if `use_arrow_keys`
                     & `use_shortcuts` are set and `(Use arrow keys)` by default.

        style: A custom color and style for the question parts. You can
               configure colors as well as font types for different elements.

        use_indicator: Flag to enable the small indicator in front of the
                       list highlighting the current location of the selection
                       cursor.

        use_shortcuts: Allow the user to select items from the list using
                       shortcuts. The shortcuts will be displayed in front of
                       the list items.

        use_arrow_keys: Allow usage of arrow keys to select item.

        use_pointer: Flag to enable the pointer in front of the currently
                     highlighted element.

    Returns:
        Question: Question instance, ready to be prompted (using `.ask()`).
    """
    if choices is None or len(choices) == 0:
        raise ValueError("A list of choices needs to be provided.")

    if use_shortcuts and len(choices) > len(InquirerControl.SHORTCUT_KEYS):
        raise ValueError("A list with shortcuts supports a maximum of {} "
                         "choices as this is the maximum number "
                         "of keyboard shortcuts that are available. You"
                         "provided {} choices!"
                         "".format(len(InquirerControl.SHORTCUT_KEYS),
                                   len(choices)))

    merged_style = merge_styles([DEFAULT_STYLE, style])

    ic = InquirerControl(
        choices,
        default,
        use_indicator=use_indicator,
        use_shortcuts=use_shortcuts,
        use_arrow_keys=use_arrow_keys,
        use_pointer=use_pointer,
        initial_choice=default,
    )

    def get_prompt_tokens():
        # noinspection PyListCreation
        tokens = [("class:qmark", qmark),
                  ("class:question", " {} ".format(message))]

        if ic.is_answered:
            if isinstance(ic.get_pointed_at().title, list):
                tokens.append((
                    "class:answer",
                    "".join([token[1] for token in ic.get_pointed_at().title]),
                ))
            else:
                tokens.append(
                    ("class:answer", " " + ic.get_pointed_at().title))
        else:
            if instruction:
                tokens.append(("class:instruction", instruction))
            else:
                if use_shortcuts and use_arrow_keys:
                    instruction_msg = " (Use shortcuts or arrow keys)"
                elif use_shortcuts and not use_arrow_keys:
                    instruction_msg = " (Use shortcuts)"
                else:
                    instruction_msg = " (Use arrow keys)"
                tokens.append(("class:instruction", instruction_msg))

        return tokens

    layout = common.create_inquirer_layout(ic, get_prompt_tokens, **kwargs)

    bindings = KeyBindings()

    @bindings.add(Keys.ControlQ, eager=True)
    @bindings.add(Keys.ControlC, eager=True)
    def _(event):
        event.app.exit(exception=KeyboardInterrupt, style="class:aborting")

    if use_shortcuts:
        # add key bindings for choices
        for i, c in enumerate(ic.choices):
            if isinstance(c, Separator):
                continue

            # noinspection PyShadowingNames
            def _reg_binding(i, keys):
                # trick out late evaluation with a "function factory":
                # https://stackoverflow.com/a/3431699
                @bindings.add(keys, eager=True)
                def select_choice(event):
                    ic.pointed_at = i

            _reg_binding(i, c.shortcut_key)

    if use_arrow_keys or use_shortcuts is False:

        @bindings.add(Keys.Down, eager=True)
        @bindings.add("j", eager=True)
        def move_cursor_down(event):
            ic.select_next()
            while not ic.is_selection_valid():
                ic.select_next()

        @bindings.add(Keys.Up, eager=True)
        @bindings.add("k", eager=True)
        def move_cursor_up(event):
            ic.select_previous()
            while not ic.is_selection_valid():
                ic.select_previous()

    @bindings.add(Keys.ControlM, eager=True)
    def set_answer(event):
        ic.is_answered = True
        event.app.exit(result=ic.get_pointed_at().value)

    @bindings.add(Keys.Any)
    def other(event):
        """Disallow inserting other text. """
        pass

    return Question(
        Application(
            layout=layout,
            key_bindings=bindings,
            style=merged_style,
            **utils.used_kwargs(kwargs, Application.__init__),
        ))
Example #5
0
def select(
    message: str,
    choices: Sequence[Union[str, Choice, Dict[str, Any]]],
    default: Optional[Union[str, Choice, Dict[str, Any]]] = None,
    qmark: str = DEFAULT_QUESTION_PREFIX,
    pointer: Optional[str] = DEFAULT_SELECTED_POINTER,
    style: Optional[Style] = None,
    use_shortcuts: bool = False,
    use_arrow_keys: bool = True,
    use_indicator: bool = False,
    use_jk_keys: bool = True,
    show_selected: bool = False,
    instruction: Optional[str] = None,
    **kwargs: Any,
) -> Question:
    """A list of items to select **one** option from.

    The user can pick one option and confirm it (if you want to allow
    the user to select multiple options, use :meth:`questionary.checkbox` instead).

    Example:
        >>> import questionary
        >>> questionary.select(
        ...     "What do you want to do?",
        ...     choices=[
        ...         "Order a pizza",
        ...         "Make a reservation",
        ...         "Ask for opening hours"
        ...     ]).ask()
        ? What do you want to do? Order a pizza
        'Order a pizza'

    .. image:: ../images/select.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 can contain :class:`Choice` or
                 or :class:`Separator` objects or simple items as strings. Passing
                 :class:`Choice` objects, allows you to configure the item more
                 (e.g. preselecting it or disabling it).

        default: A value corresponding to a selectable item in the choices,
                 to initially set the pointer position to.

        qmark: Question prefix displayed in front of the question.
               By default this is a ``?``.

        pointer: Pointer symbol in front of the currently highlighted element.
                 By default this is a ``ยป``.
                 Use ``None`` to disable it.

        instruction: A hint on how to navigate the menu.
                     It's ``(Use shortcuts)`` if only ``use_shortcuts`` is set
                     to True, ``(Use arrow keys or shortcuts)`` if ``use_arrow_keys``
                     & ``use_shortcuts`` are set and ``(Use arrow keys)`` by default.

        style: A custom color and style for the question parts. You can
               configure colors as well as font types for different elements.

        use_indicator: Flag to enable the small indicator in front of the
                       list highlighting the current location of the selection
                       cursor.

        use_shortcuts: Allow the user to select items from the list using
                       shortcuts. The shortcuts will be displayed in front of
                       the list items. Arrow keys, j/k keys and shortcuts are
                       not mutually exclusive.

        use_arrow_keys: Allow the user to select items from the list using
                        arrow keys. Arrow keys, j/k keys and shortcuts are not
                        mutually exclusive.

        use_jk_keys: Allow the user to select items from the list using
                     `j` (down) and `k` (up) keys. Arrow keys, j/k keys and
                     shortcuts are not mutually exclusive.

        show_selected: Display current selection choice at the bottom of list.

    Returns:
        :class:`Question`: Question instance, ready to be prompted (using ``.ask()``).
    """
    if not (use_arrow_keys or use_shortcuts or use_jk_keys):
        raise ValueError(
            "Some option to move the selection is required. Arrow keys, j/k keys or shortcuts."
        )

    if use_shortcuts and use_jk_keys:
        if any(getattr(c, "shortcut_key", "") in ["j", "k"] for c in choices):
            raise ValueError(
                "A choice is trying to register j/k as a "
                "shortcut key when they are in use as arrow keys "
                "disable one or the other."
            )

    if choices is None or len(choices) == 0:
        raise ValueError("A list of choices needs to be provided.")

    if use_shortcuts and len(choices) > len(InquirerControl.SHORTCUT_KEYS):
        raise ValueError(
            "A list with shortcuts supports a maximum of {} "
            "choices as this is the maximum number "
            "of keyboard shortcuts that are available. You"
            "provided {} choices!"
            "".format(len(InquirerControl.SHORTCUT_KEYS), len(choices))
        )

    merged_style = merge_styles([DEFAULT_STYLE, style])

    ic = InquirerControl(
        choices,
        default,
        pointer=pointer,
        use_indicator=use_indicator,
        use_shortcuts=use_shortcuts,
        show_selected=show_selected,
        use_arrow_keys=use_arrow_keys,
        initial_choice=default,
    )

    def get_prompt_tokens():
        # noinspection PyListCreation
        tokens = [("class:qmark", qmark), ("class:question", " {} ".format(message))]

        if ic.is_answered:
            if isinstance(ic.get_pointed_at().title, list):
                tokens.append(
                    (
                        "class:answer",
                        "".join([token[1] for token in ic.get_pointed_at().title]),
                    )
                )
            else:
                tokens.append(("class:answer", ic.get_pointed_at().title))
        else:
            if instruction:
                tokens.append(("class:instruction", instruction))
            else:
                if use_shortcuts and use_arrow_keys:
                    instruction_msg = "(Use shortcuts or arrow keys)"
                elif use_shortcuts and not use_arrow_keys:
                    instruction_msg = "(Use shortcuts)"
                else:
                    instruction_msg = "(Use arrow keys)"
                tokens.append(("class:instruction", instruction_msg))

        return tokens

    layout = common.create_inquirer_layout(ic, get_prompt_tokens, **kwargs)

    bindings = KeyBindings()

    @bindings.add(Keys.ControlQ, eager=True)
    @bindings.add(Keys.ControlC, eager=True)
    def _(event):
        event.app.exit(exception=KeyboardInterrupt, style="class:aborting")

    if use_shortcuts:
        # add key bindings for choices
        for i, c in enumerate(ic.choices):
            if c.shortcut_key is None and not c.disabled and not use_arrow_keys:
                raise RuntimeError(
                    "{} does not have a shortcut and arrow keys "
                    "for movement are disabled. "
                    "This choice is not reachable.".format(c.title)
                )
            if isinstance(c, Separator) or c.shortcut_key is None:
                continue

            # noinspection PyShadowingNames
            def _reg_binding(i, keys):
                # trick out late evaluation with a "function factory":
                # https://stackoverflow.com/a/3431699
                @bindings.add(keys, eager=True)
                def select_choice(event):
                    ic.pointed_at = i

            _reg_binding(i, c.shortcut_key)

    def move_cursor_down(event):
        ic.select_next()
        while not ic.is_selection_valid():
            ic.select_next()

    def move_cursor_up(event):
        ic.select_previous()
        while not ic.is_selection_valid():
            ic.select_previous()

    if use_arrow_keys:
        bindings.add(Keys.Down, eager=True)(move_cursor_down)
        bindings.add(Keys.Up, eager=True)(move_cursor_up)

    if use_jk_keys:
        bindings.add("j", eager=True)(move_cursor_down)
        bindings.add("k", eager=True)(move_cursor_up)

    @bindings.add(Keys.ControlM, eager=True)
    def set_answer(event):
        ic.is_answered = True
        event.app.exit(result=ic.get_pointed_at().value)

    @bindings.add(Keys.Any)
    def other(event):
        """Disallow inserting other text. """
        pass

    return Question(
        Application(
            layout=layout,
            key_bindings=bindings,
            style=merged_style,
            **utils.used_kwargs(kwargs, Application.__init__),
        )
    )
Example #6
0
def checkbox(
    message: str,
    choices: Sequence[Union[str, Choice, Dict[str, Any]]],
    default: Optional[str] = None,
    validate: Callable[[List[str]], Union[bool, str]] = lambda a: True,
    qmark: str = DEFAULT_QUESTION_PREFIX,
    style: Optional[Style] = None,
    use_pointer: bool = True,
    initial_choice: Optional[Union[str, Choice, Dict[str, Any]]] = None,
    **kwargs: Any,
) -> Question:
    """Ask the user to select from a list of items.

    This is a multiselect, the user can choose one, none or many of the
    items.

    Example:
        >>> import questionary
        >>> questionary.checkbox(
        ...    'Select toppings',
        ...    choices=[
        ...        "Cheese",
        ...        "Tomato",
        ...        "Pineapple",
        ...    ]).ask()
        ? Select toppings done (2 selections)
        ['Cheese', 'Pineapple']

    .. image:: ../images/checkbox.gif

    This is just a realy basic example, the prompt can be customised using the
    parameters.


    Args:
        message: Question text

        choices: Items shown in the selection, this can contain :class:`Choice` or
                 or :class:`Separator` objects or simple items as strings. Passing
                 :class:`Choice` objects, allows you to configure the item more
                 (e.g. preselecting it or disabling it).

        default: Default return value (single value). If you want to preselect
                 multiple items, use ``Choice("foo", checked=True)`` instead.

        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 should be a function accepting the input and
                  returning a boolean. Alternatively, the return value
                  may be a string (indicating failure), which contains
                  the error message to be displayed.

        qmark: Question prefix displayed in front of the question.
               By default this is a ``?``.

        style: A custom color and style for the question parts. You can
               configure colors as well as font types for different elements.

        use_pointer: Flag to enable the pointer in front of the currently
                     highlighted element.

        initial_choice: A value corresponding to a selectable item in the choices,
                        to initially set the pointer position to.

    Returns:
        :class:`Question`: Question instance, ready to be prompted (using ``.ask()``).
    """

    merged_style = merge_styles([
        DEFAULT_STYLE,
        # Disable the default inverted colours bottom-toolbar behaviour (for
        # the error message). However it can be re-enabled with a custom
        # style.
        Style([("bottom-toolbar", "noreverse")]),
        style,
    ])

    if not callable(validate):
        raise ValueError("validate must be callable")

    ic = InquirerControl(choices,
                         default,
                         use_pointer=use_pointer,
                         initial_choice=initial_choice)

    def get_prompt_tokens() -> List[Tuple[str, str]]:
        tokens = []

        tokens.append(("class:qmark", qmark))
        tokens.append(("class:question", " {} ".format(message)))

        if ic.is_answered:
            nbr_selected = len(ic.selected_options)
            if nbr_selected == 0:
                tokens.append(("class:answer", "done"))
            elif nbr_selected == 1:
                if isinstance(ic.get_selected_values()[0].title, list):
                    ts = ic.get_selected_values()[0].title
                    tokens.append((
                        "class:answer",
                        "".join([token[1] for token in ts]),  # type:ignore
                    ))
                else:
                    tokens.append((
                        "class:answer",
                        "[{}]".format(ic.get_selected_values()[0].title),
                    ))
            else:
                tokens.append(("class:answer",
                               "done ({} selections)".format(nbr_selected)))
        else:
            tokens.append((
                "class:instruction",
                "(Use arrow keys to move, "
                "<space> to select, "
                "<a> to toggle, "
                "<i> to invert)",
            ))
        return tokens

    def get_selected_values() -> List[Any]:
        return [c.value for c in ic.get_selected_values()]

    def perform_validation(selected_values: List[str]) -> bool:

        verdict = validate(selected_values)
        valid = verdict is True

        if not valid:
            if verdict is False:
                error_text = INVALID_INPUT
            else:
                error_text = str(verdict)

            error_message = FormattedText([("class:validation-toolbar",
                                            error_text)])

        ic.error_message = (error_message
                            if not valid and ic.submission_attempted else None)

        return valid

    layout = common.create_inquirer_layout(ic, get_prompt_tokens, **kwargs)

    bindings = KeyBindings()

    @bindings.add(Keys.ControlQ, eager=True)
    @bindings.add(Keys.ControlC, eager=True)
    def _(event):
        event.app.exit(exception=KeyboardInterrupt, style="class:aborting")

    @bindings.add(" ", eager=True)
    def toggle(_event):
        pointed_choice = ic.get_pointed_at().value
        if pointed_choice in ic.selected_options:
            ic.selected_options.remove(pointed_choice)
        else:
            ic.selected_options.append(pointed_choice)

        perform_validation(get_selected_values())

    @bindings.add("i", eager=True)
    def invert(_event):
        inverted_selection = [
            c.value for c in ic.choices if not isinstance(c, Separator)
            and c.value not in ic.selected_options and not c.disabled
        ]
        ic.selected_options = inverted_selection

        perform_validation(get_selected_values())

    @bindings.add("a", eager=True)
    def all(_event):
        all_selected = True  # all choices have been selected
        for c in ic.choices:
            if (not isinstance(c, Separator)
                    and c.value not in ic.selected_options and not c.disabled):
                # add missing ones
                ic.selected_options.append(c.value)
                all_selected = False
        if all_selected:
            ic.selected_options = []

        perform_validation(get_selected_values())

    @bindings.add(Keys.Down, eager=True)
    @bindings.add("j", eager=True)
    def move_cursor_down(_event):
        ic.select_next()
        while not ic.is_selection_valid():
            ic.select_next()

    @bindings.add(Keys.Up, eager=True)
    @bindings.add("k", eager=True)
    def move_cursor_up(_event):
        ic.select_previous()
        while not ic.is_selection_valid():
            ic.select_previous()

    @bindings.add(Keys.ControlM, eager=True)
    def set_answer(event):

        selected_values = get_selected_values()
        ic.submission_attempted = True

        if perform_validation(selected_values):
            ic.is_answered = True
            event.app.exit(result=selected_values)

    @bindings.add(Keys.Any)
    def other(_event):
        """Disallow inserting other text. """
        pass

    return Question(
        Application(
            layout=layout,
            key_bindings=bindings,
            style=merged_style,
            **utils.used_kwargs(kwargs, Application.__init__),
        ))