or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/pypi-pick

Pick an option in the terminal with a simple GUI

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
pypipkg:pypi/pick@2.4.x

To install, run

npx @tessl/cli install tessl/pypi-pick@2.4.0

index.mddocs/

Pick

A lightweight Python library for creating interactive terminal-based selection menus using the curses library. It enables developers to build command-line interfaces with intuitive selection capabilities, supporting both single-choice and multi-choice selection modes with keyboard navigation.

Package Information

  • Package Name: pick
  • Package Type: pypi
  • Language: Python
  • Installation: pip install pick
  • Version: 2.4.0

Core Imports

from pick import pick

For advanced usage:

from pick import pick, Picker, Option

Basic Usage

from pick import pick

# Simple selection
title = 'Please choose your favorite programming language: '
options = ['Java', 'JavaScript', 'Python', 'PHP', 'C++', 'Erlang', 'Haskell']
option, index = pick(options, title)
print(f"You chose {option} at index {index}")

# Multi-selection
title = 'Choose your favorite programming languages (press SPACE to mark, ENTER to continue): '
selected = pick(options, title, multiselect=True, min_selection_count=1)
print(selected)  # [('Java', 0), ('C++', 4)]

Architecture

Pick is built on Python's curses library, providing a terminal-based user interface with keyboard navigation. The architecture consists of:

  • Curses Integration: Uses the standard curses library for terminal control, with automatic fallback handling for systems with limited color support
  • Keyboard Navigation: Vi-style navigation (j/k) combined with arrow keys for intuitive movement
  • Selection Modes: Supports both single-selection and multi-selection with visual indicators
  • Screen Management: Handles scrolling for long option lists and integrates with existing curses applications
  • Event Loop: Non-blocking input handling with configurable quit keys for flexible integration

The design prioritizes simplicity while maintaining flexibility for integration into larger terminal applications.

Capabilities

Basic Selection Function

The main pick function provides a simple interface for creating interactive selection menus with support for both single and multi-selection modes.

def pick(
    options: Sequence[OPTION_T],
    title: Optional[str] = None,
    indicator: str = "*",
    default_index: int = 0,
    multiselect: bool = False,
    min_selection_count: int = 0,
    screen: Optional["curses._CursesWindow"] = None,
    position: Position = Position(0, 0),
    clear_screen: bool = True,
    quit_keys: Optional[Union[Container[int], Iterable[int]]] = None,
) -> Union[PICK_RETURN_T, List[PICK_RETURN_T]]:
    """
    Create and run an interactive picker menu.

    Parameters:
    - options: Sequence of options to choose from (strings or Option objects)
    - title: Optional title displayed above the options
    - indicator: Selection indicator string (default: "*")
    - default_index: Index of default selected option (default: 0)
    - multiselect: Enable multi-selection mode (default: False)
    - min_selection_count: Minimum selections required in multiselect mode (default: 0)
    - screen: Existing curses window object for integration (default: None)
    - position: Starting position as Position(y, x) namedtuple (default: Position(0, 0))
    - clear_screen: Whether to clear screen before drawing (default: True)
    - quit_keys: Key codes that quit the menu early (default: None)

    Returns:
    - Single selection: (option, index) tuple
    - Multi-selection: List of (option, index) tuples
    - Early quit: (None, -1) for single mode, [] for multi mode
    """

Usage Examples

# Custom indicator and default selection
option, index = pick(
    options, 
    title, 
    indicator="=>", 
    default_index=2
)

# Multi-selection with minimum requirement
selected = pick(
    options, 
    title, 
    multiselect=True, 
    min_selection_count=2
)

# With quit keys (Ctrl+C, Escape, 'q')
KEY_CTRL_C = 3
KEY_ESCAPE = 27
QUIT_KEYS = (KEY_CTRL_C, KEY_ESCAPE, ord("q"))

option, index = pick(
    options, 
    title, 
    quit_keys=QUIT_KEYS
)

Advanced Picker Class

The Picker class provides more control over the selection interface and allows for custom configuration and integration with existing curses applications.

class Picker(Generic[OPTION_T]):
    """
    Interactive picker class for terminal-based selection menus.
    """
    
    def __init__(
        self,
        options: Sequence[OPTION_T],
        title: Optional[str] = None,
        indicator: str = "*",
        default_index: int = 0,
        multiselect: bool = False,
        min_selection_count: int = 0,
        screen: Optional["curses._CursesWindow"] = None,
        position: Position = Position(0, 0),
        clear_screen: bool = True,
        quit_keys: Optional[Union[Container[int], Iterable[int]]] = None,
    ):
        """
        Initialize picker with configuration options.
        
        Parameters: Same as pick() function
        
        Raises:
        - ValueError: If options is empty
        - ValueError: If default_index >= len(options)
        - ValueError: If min_selection_count > len(options) in multiselect mode
        - ValueError: If all options are disabled
        """

    def move_up(self) -> None:
        """Move selection cursor up, wrapping to bottom and skipping disabled options."""

    def move_down(self) -> None:
        """Move selection cursor down, wrapping to top and skipping disabled options."""

    def mark_index(self) -> None:
        """Toggle selection of current option in multiselect mode."""

    def get_selected(self) -> Union[PICK_RETURN_T, List[PICK_RETURN_T]]:
        """
        Get current selection(s).
        
        Returns:
        - Single selection: (option, index) tuple
        - Multi-selection: List of (option, index) tuples
        """

    def get_title_lines(self, *, max_width: int = 80) -> List[str]:
        """Get formatted title lines with word wrapping."""

    def get_option_lines(self) -> List[str]:
        """Get formatted option lines with indicators and multi-select symbols."""

    def get_lines(self, *, max_width: int = 80) -> Tuple[List[str], int]:
        """
        Get all display lines (title + options) and current line position.
        
        Returns:
        - Tuple of (lines, current_line_position)
        """

    def draw(self, screen: "curses._CursesWindow") -> None:
        """Draw the picker UI on the screen with scroll handling."""

    def config_curses(self) -> None:
        """Configure curses settings (colors, cursor visibility)."""

    def run_loop(
        self, 
        screen: "curses._CursesWindow", 
        position: Position
    ) -> Union[PICK_RETURN_T, List[PICK_RETURN_T]]:
        """
        Run the main interaction loop handling keyboard input.
        
        Parameters:
        - screen: Curses window object
        - position: Starting position for drawing
        
        Returns: Same format as get_selected()
        """

    def start(self) -> Union[PICK_RETURN_T, List[PICK_RETURN_T]]:
        """
        Start the picker interface and return user selection.
        
        Returns: Same format as get_selected()
        """

Advanced Usage Examples

from pick import Picker, Option

# Create picker instance
picker = Picker(
    options=['Option 1', 'Option 2', 'Option 3'],
    title='Choose an option:',
    multiselect=True
)

# Programmatic navigation
picker.move_down()
picker.mark_index()  # Select current option

# Get current state
current_selection = picker.get_selected()

# Start interactive mode
final_selection = picker.start()

Option Objects

The Option class allows for rich option definitions with labels, values, descriptions, and enable/disable states.

@dataclass
class Option:
    """
    Represents a selectable option with metadata.
    """
    
    def __init__(
        self,
        label: str,
        value: Any = None,
        description: Optional[str] = None,
        enabled: bool = True
    ):
        """
        Create an option object.
        
        Parameters:
        - label: Display text for the option
        - value: Associated value (defaults to None)
        - description: Optional description shown on selection
        - enabled: Whether option can be selected (default: True)
        """
        
    label: str
    value: Any
    description: Optional[str]
    enabled: bool

Option Usage Examples

from pick import pick, Option

# Options with values and descriptions
options = [
    Option("Python", ".py", "High-level, general-purpose programming language"),
    Option("Java", ".java", "Class-based, object-oriented programming language"),
    Option("JavaScript", ".js"),
    Option("Disabled Option", enabled=False)  # This option cannot be selected
]

option, index = pick(options, "Choose a language:")
print(f"Selected: {option.label} with value: {option.value}")

# Options with complex values
database_options = [
    Option("PostgreSQL", {"driver": "psycopg2", "port": 5432}),
    Option("MySQL", {"driver": "mysql", "port": 3306}),
    Option("SQLite", {"driver": "sqlite3", "file": "db.sqlite"})
]

selected_db, _ = pick(database_options, "Choose database:")
config = selected_db.value
print(f"Using {selected_db.label} with config: {config}")

Types

from collections import namedtuple
from typing import Any, Container, Generic, Iterable, List, Optional, Sequence, Tuple, TypeVar, Union

Position = namedtuple('Position', ['y', 'x'])
"""Screen position coordinates as (y, x) tuple."""

OPTION_T = TypeVar("OPTION_T", str, Option)
"""Generic type variable for option types (string or Option objects)."""

PICK_RETURN_T = Tuple[OPTION_T, int]
"""Return type for pick results: (option, index) tuple."""

Constants

# Keyboard navigation key codes
KEYS_ENTER = (curses.KEY_ENTER, ord("\n"), ord("\r"))  # Enter, Return, Keypad Enter
KEYS_UP = (curses.KEY_UP, ord("k"))                     # Up arrow, 'k'
KEYS_DOWN = (curses.KEY_DOWN, ord("j"))                 # Down arrow, 'j'
KEYS_SELECT = (curses.KEY_RIGHT, ord(" "))              # Right arrow, Space

# Multi-select symbols
SYMBOL_CIRCLE_FILLED = "(x)"  # Selected indicator
SYMBOL_CIRCLE_EMPTY = "( )"   # Unselected indicator

Error Handling

The package raises ValueError exceptions in the following cases:

  • Empty options list: When options parameter is empty
  • Invalid default_index: When default_index >= len(options)
  • Invalid min_selection_count: When min_selection_count > len(options) in multiselect mode
  • All options disabled: When all Option objects have enabled=False
from pick import pick, Option

try:
    # This will raise ValueError
    pick([], "Choose from nothing:")
except ValueError as e:
    print(f"Error: {e}")

try:
    # This will also raise ValueError  
    disabled_options = [
        Option("Option 1", enabled=False),
        Option("Option 2", enabled=False)
    ]
    pick(disabled_options, "All disabled:")
except ValueError as e:
    print(f"Error: {e}")

Integration with Existing Curses Applications

The pick library can be integrated into existing curses applications by passing a screen object:

import curses
from pick import pick

def main(screen):
    # Your existing curses setup
    curses.curs_set(0)
    
    # Use pick within your curses app
    options = ['Option 1', 'Option 2', 'Option 3']
    option, index = pick(
        options,
        "Choose an option:",
        screen=screen,
        position=(5, 10),  # Position within your screen
        clear_screen=False  # Don't clear your existing content
    )
    
    # Continue with your curses application
    screen.addstr(0, 0, f"You selected: {option}")
    screen.refresh()

curses.wrapper(main)