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

feat: add custom_key_binding argument to add custom keyboard handlings #282

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
376d90a
Add dynamic instruction support
baturayo May 25, 2023
cc5b20e
document instruction
baturayo May 25, 2023
1a5f0e5
Merge pull request #1 from sodadata/IA-255-fork-questionary-customize…
baturayo May 25, 2023
c195d04
Merge branch 'master' of https://github.com/sodadata/ia-questionary
baturayo May 30, 2023
97dfbb2
Add custom key binding for checkbox, confirm, select and text
baturayo May 31, 2023
ea73a31
add custom key binding unit test for text field
baturayo May 31, 2023
5fb88e4
Lint isort and black
baturayo May 31, 2023
8752619
add union type to make it compatible with older python versions
baturayo May 31, 2023
2053ee4
fix docstrings
baturayo May 31, 2023
1af69a5
update documentations
baturayo May 31, 2023
cd3fabf
Add customized instruction support to confirm
baturayo Jun 1, 2023
bb0b716
Merge pull request #3 from sodadata/IA-279-add-custom-instruction-sup…
baturayo Jun 1, 2023
0bf85f4
Merge remote-tracking branch 'origin/master' into IA-222-implement-sk…
baturayo Jun 1, 2023
d980315
Merge pull request #2 from sodadata/IA-222-implement-skip-question
baturayo Jun 1, 2023
5690c80
fix local variable instruction
baturayo Jun 1, 2023
bde4f36
Update questionary/prompts/checkbox.py custom_key_bindings doc
baturayo Jun 30, 2023
2d5dc8a
Update questionary/prompts/checkbox.py variable name change
baturayo Jun 30, 2023
fc4c12a
Update questionary/prompts/confirm.py update docs
baturayo Jun 30, 2023
affe3b4
Update questionary/prompts/confirm.py variable change
baturayo Jun 30, 2023
97f7955
Update questionary/prompts/confirm.py variable change
baturayo Jun 30, 2023
3fe6719
Update questionary/prompts/text.py variable name change
baturayo Jun 30, 2023
80fa573
Update tests/prompts/test_checkbox.py variable name change
baturayo Jun 30, 2023
0696d2b
Update tests/prompts/test_confirm.py variable name change
baturayo Jun 30, 2023
456738c
Update tests/prompts/test_select.py variable name change
baturayo Jun 30, 2023
de1b9c8
Update tests/prompts/test_text.py variable name change
baturayo Jun 30, 2023
512cdb6
Merge branch 'master' of https://github.com/tmbo/questionary into IA-…
baturayo Jun 30, 2023
bcb4beb
change custom_key_binding to custom_key_bindings
baturayo Jun 30, 2023
218742e
Update questionary/prompts/checkbox.py
baturayo Jun 30, 2023
7c515a5
update custom_key_bindings documentations
baturayo Jun 30, 2023
21adee8
Merge branch 'IA-222-implement-skip-question' of https://github.com/s…
baturayo Jun 30, 2023
abdf99a
fix docs for select and text
baturayo Jun 30, 2023
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
24 changes: 24 additions & 0 deletions 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,
custom_key_bindings: Optional[Dict[Union[str, Keys], Callable]] = None,
**kwargs: Any,
) -> Question:
"""Ask the user to select from a list of items.
Expand Down Expand Up @@ -104,8 +105,28 @@ def checkbox(

use_emacs_keys: Allow the user to select items from the list using
`Ctrl+N` (down) and `Ctrl+P` (up) keys.

instruction: A message describing how to navigate the menu.

custom_key_bindings: A dictionary specifying custom key bindings for the
prompt. The dictionary should have key-value pairs,
where the key represents the key combination or key
code, and the value is a callable that will be
executed when the key is pressed. The callable
should take an ``event`` object as its argument,
which will provide information about the key event.

Examples:

- Exit with a result of ``custom`` when the user
presses :kbd:`c`::

{"c": lambda event: event.app.exit(result="custom")}

- Exit with a result of ``ctrl-q`` when the user
presses :kbd:`Ctrl` + :kbd:`q`::

{Keys.ControlQ: lambda event: event.app.exit(result="ctrl-q")}
Returns:
:class:`Question`: Question instance, ready to be prompted (using ``.ask()``).
"""
Expand Down Expand Up @@ -202,6 +223,9 @@ def perform_validation(selected_values: List[str]) -> bool:
layout = common.create_inquirer_layout(ic, get_prompt_tokens, **kwargs)

bindings = KeyBindings()
if custom_key_bindings is not None:
for key, func in custom_key_bindings.items():
bindings.add(key, eager=True)(func)

@bindings.add(Keys.ControlQ, eager=True)
@bindings.add(Keys.ControlC, eager=True)
Expand Down
29 changes: 28 additions & 1 deletion questionary/prompts/confirm.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from typing import Any
from typing import Callable
from typing import Dict
from typing import Optional
from typing import Union

from prompt_toolkit import PromptSession
from prompt_toolkit.formatted_text import to_formatted_text
Expand All @@ -22,6 +25,7 @@ def confirm(
qmark: str = DEFAULT_QUESTION_PREFIX,
style: Optional[Style] = None,
auto_enter: bool = True,
custom_key_bindings: Optional[Dict[Union[str, Keys], Callable]] = None,
instruction: Optional[str] = None,
**kwargs: Any,
) -> Question:
Expand Down Expand Up @@ -59,6 +63,26 @@ def confirm(
accept their answer. If set to `True`, a valid input will be
accepted without the need to press 'Enter'.

custom_key_bindings: A dictionary specifying custom key bindings for the
prompt. The dictionary should have key-value pairs,
where the key represents the key combination or key
code, and the value is a callable that will be
executed when the key is pressed. The callable
should take an ``event`` object as its argument,
which will provide information about the key event.

Examples:

- Exit with a result of ``custom`` when the user
presses :kbd:`c`::

{"c": lambda event: event.app.exit(result="custom")}

- Exit with a result of ``ctrl-q`` when the user
presses :kbd:`Ctrl` + :kbd:`q`::

{Keys.ControlQ: lambda event: event.app.exit(result="ctrl-q")}

instruction: A message describing how to proceed through the
confirmation prompt.
Returns:
Expand All @@ -75,7 +99,7 @@ def get_prompt_tokens():
tokens.append(("class:question", " {} ".format(message)))

if instruction is not None:
tokens.append(("class:instruction", instruction))
tokens.append(("class:instruction", "{} ".format(instruction)))
elif not status["complete"]:
_instruction = YES_OR_NO if default else NO_OR_YES
tokens.append(("class:instruction", "{} ".format(_instruction)))
Expand All @@ -91,6 +115,9 @@ def exit_with_result(event):
event.app.exit(result=status["answer"])

bindings = KeyBindings()
if custom_key_bindings is not None:
for key, func in custom_key_bindings.items():
bindings.add(key, eager=True)(func)

@bindings.add(Keys.ControlQ, eager=True)
@bindings.add(Keys.ControlC, eager=True)
Expand Down
26 changes: 26 additions & 0 deletions questionary/prompts/select.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-

from typing import Any
from typing import Callable
from typing import Dict
from typing import Optional
from typing import Sequence
Expand Down Expand Up @@ -36,6 +37,7 @@ def select(
use_emacs_keys: bool = True,
show_selected: bool = False,
instruction: Optional[str] = None,
custom_key_bindings: Optional[Dict[Union[str, Keys], Callable]] = None,
**kwargs: Any,
) -> Question:
"""A list of items to select **one** option from.
Expand Down Expand Up @@ -110,6 +112,26 @@ def select(

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

custom_key_bindings: A dictionary specifying custom key bindings for the
prompt. The dictionary should have key-value pairs,
where the key represents the key combination or key
code, and the value is a callable that will be
executed when the key is pressed. The callable
should take an ``event`` object as its argument,
which will provide information about the key event.

Examples:

- Exit with a result of ``custom`` when the user
presses :kbd:`c`::

{"c": lambda event: event.app.exit(result="custom")}

- Exit with a result of ``ctrl-q`` when the user
presses :kbd:`Ctrl` + :kbd:`q`::

{Keys.ControlQ: lambda event: event.app.exit(result="ctrl-q")}

Returns:
:class:`Question`: Question instance, ready to be prompted (using ``.ask()``).
"""
Expand Down Expand Up @@ -186,6 +208,10 @@ def get_prompt_tokens():

bindings = KeyBindings()

if custom_key_bindings is not None:
for key, func in custom_key_bindings.items():
bindings.add(key, eager=True)(func)

@bindings.add(Keys.ControlQ, eager=True)
@bindings.add(Keys.ControlC, eager=True)
def _(event):
Expand Down
32 changes: 32 additions & 0 deletions questionary/prompts/text.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
from typing import Any
from typing import Callable
from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union

from prompt_toolkit.document import Document
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.keys import Keys
from prompt_toolkit.lexers import Lexer
from prompt_toolkit.lexers import SimpleLexer
from prompt_toolkit.shortcuts.prompt import PromptSession
Expand All @@ -25,6 +30,7 @@ def text(
multiline: bool = False,
instruction: Optional[str] = None,
lexer: Optional[Lexer] = None,
custom_key_bindings: Optional[Dict[Union[str, Keys], Callable]] = None,
**kwargs: Any,
) -> Question:
"""Prompt the user to enter a free text message.
Expand Down Expand Up @@ -70,6 +76,26 @@ def text(
lexer: Supply a valid lexer to style the answer. Leave empty to
use a simple one by default.

custom_key_bindings: A dictionary specifying custom key bindings for the
prompt. The dictionary should have key-value pairs,
where the key represents the key combination or key
code, and the value is a callable that will be
executed when the key is pressed. The callable
should take an ``event`` object as its argument,
which will provide information about the key event.

Examples:

- Exit with a result of ``custom`` when the user
presses :kbd:`c`::

{"c": lambda event: event.app.exit(result="custom")}

- Exit with a result of ``ctrl-q`` when the user
presses :kbd:`Ctrl` + :kbd:`q`::

{Keys.ControlQ: lambda event: event.app.exit(result="ctrl-q")}

kwargs: Additional arguments, they will be passed to prompt toolkit.

Returns:
Expand All @@ -88,8 +114,14 @@ def get_prompt_tokens() -> List[Tuple[str, str]]:
result.append(("class:instruction", " {} ".format(instruction)))
return result

bindings = KeyBindings()
if custom_key_bindings is not None:
for key, func in custom_key_bindings.items():
bindings.add(key, eager=True)(func)

p: PromptSession = PromptSession(
get_prompt_tokens,
key_bindings=bindings,
style=merged_style,
validator=validator,
lexer=lexer,
Expand Down
14 changes: 14 additions & 0 deletions tests/prompts/test_checkbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ def test_select_with_instruction():
assert result == ["foo"]


def test_select_with_custom_key_bindings():
message = "Foo message"
kwargs = {
"choices": ["foo", "bar", "bazz"],
"custom_key_bindings": {
KeyInputs.ONE: lambda event: event.app.exit(result="1-pressed")
},
}
text = KeyInputs.ONE + "\r"

result, cli = feed_cli_with_input("checkbox", message, text, **kwargs)
assert result == "1-pressed"


def test_select_first_choice_with_token_title():
message = "Foo message"
kwargs = {
Expand Down
15 changes: 15 additions & 0 deletions tests/prompts/test_confirm.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,21 @@ def test_confirm_not_autoenter_backspace():
assert result is True


def test_confirm_with_custom_key_bindings():
message = "Foo message"
kwargs = {
"custom_key_bindings": {
KeyInputs.ONE: lambda event: event.app.exit(result="1-pressed")
}
}
text = KeyInputs.ONE + "\r"

result, cli = feed_cli_with_input(
"confirm", message, text, auto_enter=False, **kwargs
)
assert result == "1-pressed"


def test_confirm_instruction():
message = "Foo message"
text = "Y" + "\r"
Expand Down
14 changes: 14 additions & 0 deletions tests/prompts/test_select.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,20 @@ def test_select_with_instruction():
assert result == "foo"


def test_select_with_custom_key_bindings():
message = "Foo message"
kwargs = {
"choices": ["foo", "bar", "bazz"],
"custom_key_bindings": {
KeyInputs.ONE: lambda event: event.app.exit(result="1-pressed")
},
}
text = KeyInputs.ONE + "\r"

result, cli = feed_cli_with_input("select", message, text, **kwargs)
assert result == "1-pressed"


def test_cycle_to_first_choice():
message = "Foo message"
kwargs = {"choices": ["foo", "bar", "bazz"]}
Expand Down
14 changes: 14 additions & 0 deletions tests/prompts/test_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from prompt_toolkit.validation import ValidationError
from prompt_toolkit.validation import Validator

from tests.utils import KeyInputs
from tests.utils import feed_cli_with_input


Expand Down Expand Up @@ -36,6 +37,19 @@ def test_text_validate():
assert result == "Doe"


def test_text_with_custom_key_bindings():
message = "What is your name"
kwargs = {
"custom_key_bindings": {
KeyInputs.ONE: lambda event: event.app.exit(result="1-pressed")
}
}
text = KeyInputs.ONE + "\r"

result, cli = feed_cli_with_input("text", message, text, **kwargs)
assert result == "1-pressed"


def test_text_validate_with_class():
class SimpleValidator(Validator):
def validate(self, document):
Expand Down