Skip to content

Commit

Permalink
feat: allow disable cycle in list type prompts #9
Browse files Browse the repository at this point in the history
  • Loading branch information
kazhala committed Jun 14, 2021
1 parent 00b2fa0 commit 5f1a610
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 14 deletions.
33 changes: 27 additions & 6 deletions InquirerPy/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,12 +666,16 @@ def application(self, value: Application) -> None:
"""Setter for `self._application`."""
self._application = value

def _handle_down(self) -> None:
"""Handle event when user attempting to move down."""
def _handle_down(self) -> bool:
"""Handle event when user attempting to move down.
:return: Boolean indicating if the action hits the cap.
"""
if self._cycle:
self.content_control.selected_choice_index = (
self.content_control.selected_choice_index + 1
) % self.content_control.choice_count
return False
else:
self.content_control.selected_choice_index += 1
if (
Expand All @@ -681,17 +685,25 @@ def _handle_down(self) -> None:
self.content_control.selected_choice_index = (
self.content_control.choice_count - 1
)
return True
return False

def _handle_up(self) -> None:
"""Handle event when user attempting to move down."""
def _handle_up(self) -> bool:
"""Handle event when user attempting to move down.
:return: Boolean indicating if the action hits the cap.
"""
if self._cycle:
self.content_control.selected_choice_index = (
self.content_control.selected_choice_index - 1
) % self.content_control.choice_count
return False
else:
self.content_control.selected_choice_index -= 1
if self.content_control.selected_choice_index < 0:
self.content_control.selected_choice_index = 0
return True
return False

@abstractmethod
def _handle_enter(self, event) -> None:
Expand Down Expand Up @@ -730,6 +742,7 @@ class BaseListPrompt(BaseComplexPrompt):
:param invalid_message: Message to display when input is invalid.
:param multiselect: Enable multiselect mode.
:param keybindings: Custom keybindings to apply.
:param cycle: Return to top item if hit bottom or vice versa.
:param show_cursor: Display cursor at the end of the prompt.
"""

Expand Down Expand Up @@ -855,16 +868,24 @@ def _toggle_all(self, value: bool = None) -> None:
def _handle_up(self) -> None:
"""Handle the event when user attempt to move up."""
while True:
super()._handle_up()
cap = super()._handle_up()
if not isinstance(self.content_control.selection["value"], Separator):
break
else:
if cap and not self._cycle:
self._handle_down()
break

def _handle_down(self) -> None:
"""Handle the event when user attempt to move down."""
while True:
super()._handle_down()
cap = super()._handle_down()
if not isinstance(self.content_control.selection["value"], Separator):
break
else:
if cap and not self._cycle:
self._handle_up()
break

def _handle_enter(self, event) -> None:
"""Handle the event when user hit Enter key.
Expand Down
3 changes: 3 additions & 0 deletions InquirerPy/prompts/checkbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ class CheckboxPrompt(BaseListPrompt):
:param invalid_message: Message to display when input is invalid.
:param keybindings: Custom keybindings to apply.
:param show_cursor: Display cursor at the end of the prompt.
:param cycle: Return to top item if hit bottom or vice versa.
"""

def __init__(
Expand All @@ -124,6 +125,7 @@ def __init__(
invalid_message: str = "Invalid input",
keybindings: Dict[str, List[Dict[str, Any]]] = None,
show_cursor: bool = True,
cycle: bool = True,
session_result: SessionResult = None,
) -> None:
"""Initialise the content_control and create Application."""
Expand All @@ -150,6 +152,7 @@ def __init__(
multiselect=True,
keybindings=keybindings,
show_cursor=show_cursor,
cycle=cycle,
session_result=session_result,
)

Expand Down
27 changes: 20 additions & 7 deletions InquirerPy/prompts/expand.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from prompt_toolkit.validation import Validator

from InquirerPy.base import BaseListPrompt, InquirerPyUIControl
from InquirerPy.base import BaseComplexPrompt, BaseListPrompt, InquirerPyUIControl
from InquirerPy.enum import INQUIRERPY_POINTER_SEQUENCE
from InquirerPy.exceptions import InvalidArgument, RequiredKeyNotFound
from InquirerPy.separator import Separator
Expand Down Expand Up @@ -175,6 +175,7 @@ class ExpandPrompt(BaseListPrompt):
:param invalid_message: Message to display when input is invalid.
:param keybindings: Custom keybindings to apply.
:param show_cursor: Display cursor at the end of the prompt.
:param cycle: Return to top item if hit bottom or vice versa.
"""

def __init__(
Expand All @@ -201,6 +202,7 @@ def __init__(
invalid_message: str = "Invalid input",
keybindings: Dict[str, List[Dict[str, Any]]] = None,
show_cursor: bool = True,
cycle: bool = True,
session_result: SessionResult = None,
) -> None:
"""Create the application and apply keybindings."""
Expand Down Expand Up @@ -231,6 +233,7 @@ def __init__(
multiselect=multiselect,
keybindings=keybindings,
show_cursor=show_cursor,
cycle=cycle,
session_result=session_result,
)

Expand Down Expand Up @@ -270,13 +273,15 @@ def _handle_up(self) -> None:
if not self.content_control._expanded:
return
while True:
self.content_control.selected_choice_index = (
self.content_control.selected_choice_index - 1
) % self.content_control.choice_count
cap = BaseComplexPrompt._handle_up(self)
if not isinstance(
self.content_control.selection["value"], Separator
) and not isinstance(self.content_control.selection["value"], ExpandHelp):
break
else:
if cap and not self._cycle:
self._handle_down()
break

def _handle_down(self) -> None:
"""Handle the event when user attempt to move down.
Expand All @@ -286,13 +291,21 @@ def _handle_down(self) -> None:
if not self.content_control._expanded:
return
while True:
self.content_control.selected_choice_index = (
self.content_control.selected_choice_index + 1
) % self.content_control.choice_count
cap = BaseComplexPrompt._handle_down(self)
if not isinstance(
self.content_control.selection["value"], Separator
) and not isinstance(self.content_control.selection["value"], ExpandHelp):
break
elif (
isinstance(self.content_control.selection["value"], ExpandHelp)
and not self._cycle
):
self._handle_up()
break
else:
if cap and not self._cycle:
self._handle_up()
break

@property
def instruction(self) -> str:
Expand Down
3 changes: 3 additions & 0 deletions InquirerPy/prompts/fuzzy/fuzzy.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ class FuzzyPrompt(BaseComplexPrompt):
:param validate: A callable or Validator instance to validate user selection.
:param invalid_message: Message to display when input is invalid.
:param keybindings: Custom keybindings to apply.
:param cycle: Return to top item if hit bottom or vice versa.
"""

def __init__(
Expand All @@ -279,6 +280,7 @@ def __init__(
validate: Union[Callable[[Any], bool], Validator] = None,
invalid_message: str = "Invalid input",
keybindings: Dict[str, List[Dict[str, Any]]] = None,
cycle: bool = True,
session_result: SessionResult = None,
) -> None:
if not keybindings:
Expand Down Expand Up @@ -307,6 +309,7 @@ def __init__(
multiselect=multiselect,
instruction=instruction,
keybindings=keybindings,
cycle=cycle,
session_result=session_result,
)
self._default = default if not isinstance(default, Callable) else default(self._result) # type: ignore
Expand Down
3 changes: 3 additions & 0 deletions InquirerPy/prompts/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class ListPrompt(BaseListPrompt):
:param invalid_message: Message to display when input is invalid.
:param keybindings: Custom keybindings to apply.
:param show_cursor: Display cursor at the end of the prompt.
:param cycle: Return to top item if hit bottom or vice versa.
"""

def __init__(
Expand All @@ -118,6 +119,7 @@ def __init__(
invalid_message: str = "Invalid input",
keybindings: Dict[str, List[Dict[str, Any]]] = None,
show_cursor: bool = True,
cycle: bool = True,
session_result: SessionResult = None,
) -> None:
"""Initialise the content_control and create Application."""
Expand Down Expand Up @@ -146,5 +148,6 @@ def __init__(
multiselect=multiselect,
keybindings=keybindings,
show_cursor=show_cursor,
cycle=cycle,
session_result=session_result,
)
3 changes: 3 additions & 0 deletions InquirerPy/prompts/rawlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ class RawlistPrompt(BaseListPrompt):
:param invalid_message: Message to display when input is invalid.
:param keybindings: Custom keybindings to apply.
:param show_cursor: Display cursor at the end of the prompt.
:param cycle: Return to top item if hit bottom or vice versa.
"""

def __init__(
Expand All @@ -153,6 +154,7 @@ def __init__(
invalid_message: str = "Invalid input",
keybindings: Dict[str, List[Dict[str, Any]]] = None,
show_cursor: bool = True,
cycle: bool = True,
session_result: SessionResult = None,
) -> None:
"""Construct content control and initialise the application while also apply keybindings."""
Expand Down Expand Up @@ -182,6 +184,7 @@ def __init__(
invalid_message=invalid_message,
keybindings=keybindings,
show_cursor=show_cursor,
cycle=cycle,
session_result=session_result,
)

Expand Down
3 changes: 3 additions & 0 deletions examples/example_checkbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

def question1_choice(_):
return [
Separator(),
{"name": "Sydney", "value": "ap-southeast-2", "enabled": True},
{"name": "Singapore", "value": "ap-southeast-1", "enabled": False},
Separator(),
"us-east-1",
"us-west-1",
Separator(),
]


Expand Down Expand Up @@ -50,6 +52,7 @@ def alternate():
regions = inquirer.checkbox(
message="Select regions:",
choices=question1_choice,
cycle=False,
transformer=lambda result: "%s region%s selected"
% (len(result), "s" if len(result) > 1 else ""),
).execute()
Expand Down
3 changes: 2 additions & 1 deletion examples/example_expand.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from InquirerPy import prompt, inquirer
from InquirerPy import inquirer, prompt
from InquirerPy.separator import Separator


Expand Down Expand Up @@ -31,6 +31,7 @@ def classic():
"choices": question1_choice,
"message": "Pick your favourite:",
"default": "o",
"cycle": False,
},
{
"type": "expand",
Expand Down

0 comments on commit 5f1a610

Please sign in to comment.