Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds the description and show_description options to Choice and InquirerControl #330

Merged
merged 9 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion questionary/prompts/checkbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def checkbox(
use_jk_keys: bool = True,
use_emacs_keys: bool = True,
instruction: Optional[str] = None,
show_description: bool = True,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@viniciusdc I've changed this to True by default, because that is what the user expects when they set a description on a Choice. Plus, since there are no descriptions yet, previous code is not affected by this.

**kwargs: Any,
) -> Question:
"""Ask the user to select from a list of items.
Expand Down Expand Up @@ -106,6 +107,8 @@ def checkbox(
`Ctrl+N` (down) and `Ctrl+P` (up) keys.
instruction: A message describing how to navigate the menu.

show_description: Display description of current selection if available.

Returns:
:class:`Question`: Question instance, ready to be prompted (using ``.ask()``).
"""
Expand All @@ -130,7 +133,11 @@ def checkbox(
raise ValueError("validate must be callable")

ic = InquirerControl(
choices, default, pointer=pointer, initial_choice=initial_choice
choices,
default,
pointer=pointer,
initial_choice=initial_choice,
show_description=show_description,
)

def get_prompt_tokens() -> List[Tuple[str, str]]:
Expand Down
25 changes: 22 additions & 3 deletions questionary/prompts/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class Choice:
checked: Preselect this choice when displaying the options.

shortcut_key: Key shortcut used to select this item.

description: Optional description of the item that can be displayed.
"""

title: FormattedText
Expand All @@ -70,17 +72,22 @@ class Choice:
shortcut_key: Optional[str]
"""A shortcut key for the choice"""

description: Optional[str]
"""Choice description"""

def __init__(
self,
title: FormattedText,
value: Optional[Any] = None,
disabled: Optional[str] = None,
checked: Optional[bool] = False,
shortcut_key: Optional[Union[str, bool]] = True,
description: Optional[str] = None,
) -> None:
self.disabled = disabled
self.title = title
self.checked = checked if checked is not None else False
self.description = description

if value is not None:
self.value = value
Expand Down Expand Up @@ -124,6 +131,7 @@ def build(c: Union[str, "Choice", Dict[str, Any]]) -> "Choice":
c.get("disabled", None),
c.get("checked"),
c.get("key"),
c.get("description", None),
)

def get_shortcut_title(self):
Expand Down Expand Up @@ -202,6 +210,7 @@ class InquirerControl(FormattedTextControl):
pointer: Optional[str]
pointed_at: int
is_answered: bool
show_description: bool

def __init__(
self,
Expand All @@ -211,13 +220,15 @@ def __init__(
use_indicator: bool = True,
use_shortcuts: bool = False,
show_selected: bool = False,
show_description: bool = True,
use_arrow_keys: bool = True,
initial_choice: Optional[Union[str, Choice, Dict[str, Any]]] = None,
**kwargs: Any,
):
self.use_indicator = use_indicator
self.use_shortcuts = use_shortcuts
self.show_selected = show_selected
self.show_description = show_description
self.use_arrow_keys = use_arrow_keys
self.default = default
self.pointer = pointer
Expand Down Expand Up @@ -417,18 +428,26 @@ def append(index: int, choice: Choice):
for i, c in enumerate(self.choices):
append(i, c)

if self.show_selected:
current = self.get_pointed_at()
current = self.get_pointed_at()

if self.show_selected:
answer = current.get_shortcut_title() if self.use_shortcuts else ""

answer += (
current.title if isinstance(current.title, str) else current.title[0][1]
)

tokens.append(("class:text", " Answer: {}".format(answer)))
else:

show_description = self.show_description and current.description is not None
if show_description:
tokens.append(
("class:text", " Description: {}".format(current.description))
)

if not (self.show_selected or show_description):
tokens.pop() # Remove last newline.

return tokens

def is_selection_a_separator(self) -> bool:
Expand Down
4 changes: 4 additions & 0 deletions questionary/prompts/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def select(
use_jk_keys: bool = True,
use_emacs_keys: bool = True,
show_selected: bool = False,
show_description: bool = True,
instruction: Optional[str] = None,
**kwargs: Any,
) -> Question:
Expand Down Expand Up @@ -110,6 +111,8 @@ def select(

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

show_description: Display description of current selection if available.

Returns:
:class:`Question`: Question instance, ready to be prompted (using ``.ask()``).
"""
Expand Down Expand Up @@ -150,6 +153,7 @@ def select(
use_indicator=use_indicator,
use_shortcuts=use_shortcuts,
show_selected=show_selected,
show_description=show_description,
use_arrow_keys=use_arrow_keys,
initial_choice=default,
)
Expand Down
40 changes: 40 additions & 0 deletions tests/prompts/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,43 @@ def test_print_with_style(monkeypatch):

assert mock.method_calls[1][0] == "write"
assert mock.method_calls[1][1][0] == "Hello World"


def test_prompt_show_description():
ic = InquirerControl(
["a", Choice("b", description="B")],
show_selected=True,
show_description=True,
Comment on lines +230 to +231
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having both active asserts that they can coexist.

)

expected_tokens = [
("class:pointer", " » "),
("[SetCursorPosition]", ""),
("class:text", "○ "),
("class:highlighted", "a"),
("", "\n"),
("class:text", " "),
("class:text", "○ "),
("class:text", "b"),
("", "\n"),
("class:text", " Answer: a"),
]
assert ic.pointed_at == 0
assert ic._get_choice_tokens() == expected_tokens

ic.select_next()
expected_tokens = [
("class:text", " "),
("class:text", "○ "),
("class:text", "a"),
("", "\n"),
("class:pointer", " » "),
("[SetCursorPosition]", ""),
("class:text", "○ "),
("class:highlighted", "b"),
("", "\n"),
("class:text", " Answer: b"),
("class:text", " Description: B"),
]
assert ic.pointed_at == 1
assert ic._get_choice_tokens() == expected_tokens
Loading