From 6ef72c28df49d11c191a9f4d5209a14a43df089b Mon Sep 17 00:00:00 2001 From: Henri Date: Tue, 7 May 2024 11:20:53 +0200 Subject: [PATCH 1/9] feat: added ability to quit early --- src/pick/__init__.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/pick/__init__.py b/src/pick/__init__.py index d730b1d..3895652 100644 --- a/src/pick/__init__.py +++ b/src/pick/__init__.py @@ -13,6 +13,9 @@ class Option: description: Optional[str] = None +KEY_CTRL_C = 3 +KEY_ESCAPE = 27 +KEYS_QUIT = (KEY_CTRL_C, KEY_ESCAPE, ord("q")) KEYS_ENTER = (curses.KEY_ENTER, ord("\n"), ord("\r")) KEYS_UP = (curses.KEY_UP, ord("k")) KEYS_DOWN = (curses.KEY_DOWN, ord("j")) @@ -24,7 +27,8 @@ class Option: OPTION_T = TypeVar("OPTION_T", str, Option) PICK_RETURN_T = Tuple[OPTION_T, int] -Position = namedtuple('Position', ['y', 'x']) +Position = namedtuple("Position", ["y", "x"]) + @dataclass class Picker(Generic[OPTION_T]): @@ -169,7 +173,9 @@ def draw(self, screen: "curses._CursesWindow") -> None: option = self.options[self.index] if isinstance(option, Option) and option.description is not None: - description_lines = self.get_description_lines(option.description, max_x // 2) + description_lines = self.get_description_lines( + option.description, max_x // 2 + ) for i, line in enumerate(description_lines): screen.addnstr(i + 3, max_x // 2, line, 2 * max_x // 2 - 2) @@ -182,7 +188,9 @@ def run_loop( while True: self.draw(screen) c = screen.getch() - if c in KEYS_UP: + if c in KEYS_QUIT: + return None, None + elif c in KEYS_UP: self.move_up() elif c in KEYS_DOWN: self.move_down() @@ -231,7 +239,7 @@ def pick( min_selection_count: int = 0, screen: Optional["curses._CursesWindow"] = None, position: Position = Position(0, 0), - clear_screen = True, + clear_screen: bool = True, ): picker: Picker = Picker( options, From 9c986166a77b4e09170a58ccc8d21e9b90e2fcd4 Mon Sep 17 00:00:00 2001 From: Henri Date: Tue, 7 May 2024 13:38:16 +0200 Subject: [PATCH 2/9] fix: return type compliance --- src/pick/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pick/__init__.py b/src/pick/__init__.py index 3895652..ccac224 100644 --- a/src/pick/__init__.py +++ b/src/pick/__init__.py @@ -189,7 +189,7 @@ def run_loop( self.draw(screen) c = screen.getch() if c in KEYS_QUIT: - return None, None + return None, -1 elif c in KEYS_UP: self.move_up() elif c in KEYS_DOWN: From ef6ccf9b41fb0404d419dd89c6977aedc8e0faf4 Mon Sep 17 00:00:00 2001 From: Henri Date: Fri, 10 May 2024 10:06:12 +0200 Subject: [PATCH 3/9] chores: reverted undesired autoformat --- src/pick/__init__.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/pick/__init__.py b/src/pick/__init__.py index ccac224..627cbdf 100644 --- a/src/pick/__init__.py +++ b/src/pick/__init__.py @@ -27,8 +27,7 @@ class Option: OPTION_T = TypeVar("OPTION_T", str, Option) PICK_RETURN_T = Tuple[OPTION_T, int] -Position = namedtuple("Position", ["y", "x"]) - +Position = namedtuple('Position', ['y', 'x']) @dataclass class Picker(Generic[OPTION_T]): @@ -173,9 +172,7 @@ def draw(self, screen: "curses._CursesWindow") -> None: option = self.options[self.index] if isinstance(option, Option) and option.description is not None: - description_lines = self.get_description_lines( - option.description, max_x // 2 - ) + description_lines = self.get_description_lines(option.description, max_x // 2) for i, line in enumerate(description_lines): screen.addnstr(i + 3, max_x // 2, line, 2 * max_x // 2 - 2) From 6c2742bc24eb8f7c91efc1a9d53455c327e6e417 Mon Sep 17 00:00:00 2001 From: Henri Date: Tue, 11 Jun 2024 09:24:19 +0200 Subject: [PATCH 4/9] fix: quitting disabled by default and multiselect taken into account --- src/pick/__init__.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/pick/__init__.py b/src/pick/__init__.py index 627cbdf..255127e 100644 --- a/src/pick/__init__.py +++ b/src/pick/__init__.py @@ -13,9 +13,6 @@ class Option: description: Optional[str] = None -KEY_CTRL_C = 3 -KEY_ESCAPE = 27 -KEYS_QUIT = (KEY_CTRL_C, KEY_ESCAPE, ord("q")) KEYS_ENTER = (curses.KEY_ENTER, ord("\n"), ord("\r")) KEYS_UP = (curses.KEY_UP, ord("k")) KEYS_DOWN = (curses.KEY_DOWN, ord("j")) @@ -42,6 +39,7 @@ class Picker(Generic[OPTION_T]): screen: Optional["curses._CursesWindow"] = None position: Position = Position(0, 0) clear_screen: bool = True + keys_quit: Optional[tuple[int]] = None def __post_init__(self) -> None: if len(self.options) == 0: @@ -185,8 +183,11 @@ def run_loop( while True: self.draw(screen) c = screen.getch() - if c in KEYS_QUIT: - return None, -1 + if self.keys_quit is not None and c in self.keys_quit: + if self.multiselect: + return [] + else: + return None, -1 elif c in KEYS_UP: self.move_up() elif c in KEYS_DOWN: @@ -237,6 +238,7 @@ def pick( screen: Optional["curses._CursesWindow"] = None, position: Position = Position(0, 0), clear_screen: bool = True, + keys_quit: Optional[tuple[int]] = None, ): picker: Picker = Picker( options, @@ -248,5 +250,6 @@ def pick( screen, position, clear_screen, + keys_quit, ) return picker.start() From 9ac1334549dbd920932f3011c686b575a13662de Mon Sep 17 00:00:00 2001 From: Henri Date: Tue, 18 Jun 2024 13:28:01 +0200 Subject: [PATCH 5/9] fix: tuple was not subscriptable in old versions of python --- src/pick/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pick/__init__.py b/src/pick/__init__.py index 255127e..d0bb5de 100644 --- a/src/pick/__init__.py +++ b/src/pick/__init__.py @@ -238,7 +238,7 @@ def pick( screen: Optional["curses._CursesWindow"] = None, position: Position = Position(0, 0), clear_screen: bool = True, - keys_quit: Optional[tuple[int]] = None, + keys_quit: Optional[Tuple[int]] = None, ): picker: Picker = Picker( options, From cbd8b3d0c80fe4702daf806995728727eea4604c Mon Sep 17 00:00:00 2001 From: Henri Date: Tue, 18 Jun 2024 13:49:57 +0200 Subject: [PATCH 6/9] fix: tuple was not subscriptable in old versions of python --- src/pick/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pick/__init__.py b/src/pick/__init__.py index d0bb5de..40b8b8d 100644 --- a/src/pick/__init__.py +++ b/src/pick/__init__.py @@ -39,7 +39,7 @@ class Picker(Generic[OPTION_T]): screen: Optional["curses._CursesWindow"] = None position: Position = Position(0, 0) clear_screen: bool = True - keys_quit: Optional[tuple[int]] = None + keys_quit: Optional[Tuple[int]] = None def __post_init__(self) -> None: if len(self.options) == 0: From 5d5366e0ad50878fe1f69639415b10a85a9c7ee3 Mon Sep 17 00:00:00 2001 From: Henri Date: Thu, 27 Jun 2024 07:42:10 +0200 Subject: [PATCH 7/9] keys_quit -> quit_keys and type hint more general --- src/pick/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pick/__init__.py b/src/pick/__init__.py index 40b8b8d..5cd8a84 100644 --- a/src/pick/__init__.py +++ b/src/pick/__init__.py @@ -1,7 +1,7 @@ import curses from collections import namedtuple from dataclasses import dataclass, field -from typing import Any, Generic, List, Optional, Sequence, Tuple, TypeVar, Union +from typing import Any, Container, Generic, Iterable, List, Optional, Sequence, Tuple, TypeVar, Union __all__ = ["Picker", "pick", "Option"] @@ -39,7 +39,7 @@ class Picker(Generic[OPTION_T]): screen: Optional["curses._CursesWindow"] = None position: Position = Position(0, 0) clear_screen: bool = True - keys_quit: Optional[Tuple[int]] = None + quit_keys: Optional[Union[Container[int], Iterable[int]]] = None def __post_init__(self) -> None: if len(self.options) == 0: @@ -183,7 +183,7 @@ def run_loop( while True: self.draw(screen) c = screen.getch() - if self.keys_quit is not None and c in self.keys_quit: + if self.quit_keys is not None and c in self.quit_keys: if self.multiselect: return [] else: @@ -238,7 +238,7 @@ def pick( screen: Optional["curses._CursesWindow"] = None, position: Position = Position(0, 0), clear_screen: bool = True, - keys_quit: Optional[Tuple[int]] = None, + quit_keys: Optional[Union[Container[int], Iterable[int]]] = None, ): picker: Picker = Picker( options, @@ -250,6 +250,6 @@ def pick( screen, position, clear_screen, - keys_quit, + quit_keys, ) return picker.start() From b7d083266efd64f522a5a5533b8b2a887d330496 Mon Sep 17 00:00:00 2001 From: Henri Date: Thu, 27 Jun 2024 07:42:27 +0200 Subject: [PATCH 8/9] doc: added example and documented option in readme --- README.md | 4 +++- example/basic.py | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4475d21..8b70548 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,10 @@ interactive selection list in the terminal. multiple items by hitting SPACE - `min_selection_count`: (optional) for multi select feature to dictate a minimum of selected items before continuing -- `screen`: (optional), if you are using `pick` within an existing curses application set this to your existing `screen` object. It is assumed this has initialised in the standard way (e.g. via `curses.wrapper()`, or `curses.noecho(); curses.cbreak(); screen.kepad(True)`) +- `screen`: (optional), if you are using `pick` within an existing curses application set this to your existing `screen` object. It is assumed this has initialised in the standard way (e.g. via `curses.wrapper()`, or `curses.noecho(); curses.cbreak(); screen.kepad(True)`) - `position`: (optional), if you are using `pick` within an existing curses application use this to set the first position to write to. e.g., `position=pick.Position(y=1, x=1)` +- `quit_keys`: (optional), if you want to quit early, you can pass a key codes. + If the corresponding key are pressed, it will quit the menu ## Community Projects diff --git a/example/basic.py b/example/basic.py index e1db466..fad055d 100644 --- a/example/basic.py +++ b/example/basic.py @@ -1,6 +1,12 @@ from pick import pick +KEY_CTRL_C = 3 +KEY_ESCAPE = 27 +QUIT_KEYS = (KEY_CTRL_C, KEY_ESCAPE, ord("q")) + title = "Please choose your favorite programming language: " options = ["Java", "JavaScript", "Python", "PHP", "C++", "Erlang", "Haskell"] -option, index = pick(options, title, indicator="=>", default_index=2) +option, index = pick( + options, title, indicator="=>", default_index=2, quit_keys=QUIT_KEYS +) print(f"You chose {option} at index {index}") From 65f7465665c2c45bd9542eb62e6130ebda3413ec Mon Sep 17 00:00:00 2001 From: AN Long Date: Thu, 27 Jun 2024 22:13:03 +0800 Subject: [PATCH 9/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b70548..0612fed 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ interactive selection list in the terminal. - `screen`: (optional), if you are using `pick` within an existing curses application set this to your existing `screen` object. It is assumed this has initialised in the standard way (e.g. via `curses.wrapper()`, or `curses.noecho(); curses.cbreak(); screen.kepad(True)`) - `position`: (optional), if you are using `pick` within an existing curses application use this to set the first position to write to. e.g., `position=pick.Position(y=1, x=1)` - `quit_keys`: (optional), if you want to quit early, you can pass a key codes. - If the corresponding key are pressed, it will quit the menu + If the corresponding key are pressed, it will quit the menu. ## Community Projects