or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.md

index.mddocs/

0

# Pick

1

2

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.

3

4

## Package Information

5

6

- **Package Name**: pick

7

- **Package Type**: pypi

8

- **Language**: Python

9

- **Installation**: `pip install pick`

10

- **Version**: 2.4.0

11

12

## Core Imports

13

14

```python

15

from pick import pick

16

```

17

18

For advanced usage:

19

20

```python

21

from pick import pick, Picker, Option

22

```

23

24

## Basic Usage

25

26

```python

27

from pick import pick

28

29

# Simple selection

30

title = 'Please choose your favorite programming language: '

31

options = ['Java', 'JavaScript', 'Python', 'PHP', 'C++', 'Erlang', 'Haskell']

32

option, index = pick(options, title)

33

print(f"You chose {option} at index {index}")

34

35

# Multi-selection

36

title = 'Choose your favorite programming languages (press SPACE to mark, ENTER to continue): '

37

selected = pick(options, title, multiselect=True, min_selection_count=1)

38

print(selected) # [('Java', 0), ('C++', 4)]

39

```

40

41

## Architecture

42

43

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

44

45

- **Curses Integration**: Uses the standard curses library for terminal control, with automatic fallback handling for systems with limited color support

46

- **Keyboard Navigation**: Vi-style navigation (j/k) combined with arrow keys for intuitive movement

47

- **Selection Modes**: Supports both single-selection and multi-selection with visual indicators

48

- **Screen Management**: Handles scrolling for long option lists and integrates with existing curses applications

49

- **Event Loop**: Non-blocking input handling with configurable quit keys for flexible integration

50

51

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

52

53

## Capabilities

54

55

### Basic Selection Function

56

57

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

58

59

```python { .api }

60

def pick(

61

options: Sequence[OPTION_T],

62

title: Optional[str] = None,

63

indicator: str = "*",

64

default_index: int = 0,

65

multiselect: bool = False,

66

min_selection_count: int = 0,

67

screen: Optional["curses._CursesWindow"] = None,

68

position: Position = Position(0, 0),

69

clear_screen: bool = True,

70

quit_keys: Optional[Union[Container[int], Iterable[int]]] = None,

71

) -> Union[PICK_RETURN_T, List[PICK_RETURN_T]]:

72

"""

73

Create and run an interactive picker menu.

74

75

Parameters:

76

- options: Sequence of options to choose from (strings or Option objects)

77

- title: Optional title displayed above the options

78

- indicator: Selection indicator string (default: "*")

79

- default_index: Index of default selected option (default: 0)

80

- multiselect: Enable multi-selection mode (default: False)

81

- min_selection_count: Minimum selections required in multiselect mode (default: 0)

82

- screen: Existing curses window object for integration (default: None)

83

- position: Starting position as Position(y, x) namedtuple (default: Position(0, 0))

84

- clear_screen: Whether to clear screen before drawing (default: True)

85

- quit_keys: Key codes that quit the menu early (default: None)

86

87

Returns:

88

- Single selection: (option, index) tuple

89

- Multi-selection: List of (option, index) tuples

90

- Early quit: (None, -1) for single mode, [] for multi mode

91

"""

92

```

93

94

#### Usage Examples

95

96

```python

97

# Custom indicator and default selection

98

option, index = pick(

99

options,

100

title,

101

indicator="=>",

102

default_index=2

103

)

104

105

# Multi-selection with minimum requirement

106

selected = pick(

107

options,

108

title,

109

multiselect=True,

110

min_selection_count=2

111

)

112

113

# With quit keys (Ctrl+C, Escape, 'q')

114

KEY_CTRL_C = 3

115

KEY_ESCAPE = 27

116

QUIT_KEYS = (KEY_CTRL_C, KEY_ESCAPE, ord("q"))

117

118

option, index = pick(

119

options,

120

title,

121

quit_keys=QUIT_KEYS

122

)

123

```

124

125

### Advanced Picker Class

126

127

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

128

129

```python { .api }

130

class Picker(Generic[OPTION_T]):

131

"""

132

Interactive picker class for terminal-based selection menus.

133

"""

134

135

def __init__(

136

self,

137

options: Sequence[OPTION_T],

138

title: Optional[str] = None,

139

indicator: str = "*",

140

default_index: int = 0,

141

multiselect: bool = False,

142

min_selection_count: int = 0,

143

screen: Optional["curses._CursesWindow"] = None,

144

position: Position = Position(0, 0),

145

clear_screen: bool = True,

146

quit_keys: Optional[Union[Container[int], Iterable[int]]] = None,

147

):

148

"""

149

Initialize picker with configuration options.

150

151

Parameters: Same as pick() function

152

153

Raises:

154

- ValueError: If options is empty

155

- ValueError: If default_index >= len(options)

156

- ValueError: If min_selection_count > len(options) in multiselect mode

157

- ValueError: If all options are disabled

158

"""

159

160

def move_up(self) -> None:

161

"""Move selection cursor up, wrapping to bottom and skipping disabled options."""

162

163

def move_down(self) -> None:

164

"""Move selection cursor down, wrapping to top and skipping disabled options."""

165

166

def mark_index(self) -> None:

167

"""Toggle selection of current option in multiselect mode."""

168

169

def get_selected(self) -> Union[PICK_RETURN_T, List[PICK_RETURN_T]]:

170

"""

171

Get current selection(s).

172

173

Returns:

174

- Single selection: (option, index) tuple

175

- Multi-selection: List of (option, index) tuples

176

"""

177

178

def get_title_lines(self, *, max_width: int = 80) -> List[str]:

179

"""Get formatted title lines with word wrapping."""

180

181

def get_option_lines(self) -> List[str]:

182

"""Get formatted option lines with indicators and multi-select symbols."""

183

184

def get_lines(self, *, max_width: int = 80) -> Tuple[List[str], int]:

185

"""

186

Get all display lines (title + options) and current line position.

187

188

Returns:

189

- Tuple of (lines, current_line_position)

190

"""

191

192

def draw(self, screen: "curses._CursesWindow") -> None:

193

"""Draw the picker UI on the screen with scroll handling."""

194

195

def config_curses(self) -> None:

196

"""Configure curses settings (colors, cursor visibility)."""

197

198

def run_loop(

199

self,

200

screen: "curses._CursesWindow",

201

position: Position

202

) -> Union[PICK_RETURN_T, List[PICK_RETURN_T]]:

203

"""

204

Run the main interaction loop handling keyboard input.

205

206

Parameters:

207

- screen: Curses window object

208

- position: Starting position for drawing

209

210

Returns: Same format as get_selected()

211

"""

212

213

def start(self) -> Union[PICK_RETURN_T, List[PICK_RETURN_T]]:

214

"""

215

Start the picker interface and return user selection.

216

217

Returns: Same format as get_selected()

218

"""

219

```

220

221

#### Advanced Usage Examples

222

223

```python

224

from pick import Picker, Option

225

226

# Create picker instance

227

picker = Picker(

228

options=['Option 1', 'Option 2', 'Option 3'],

229

title='Choose an option:',

230

multiselect=True

231

)

232

233

# Programmatic navigation

234

picker.move_down()

235

picker.mark_index() # Select current option

236

237

# Get current state

238

current_selection = picker.get_selected()

239

240

# Start interactive mode

241

final_selection = picker.start()

242

```

243

244

### Option Objects

245

246

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

247

248

```python { .api }

249

@dataclass

250

class Option:

251

"""

252

Represents a selectable option with metadata.

253

"""

254

255

def __init__(

256

self,

257

label: str,

258

value: Any = None,

259

description: Optional[str] = None,

260

enabled: bool = True

261

):

262

"""

263

Create an option object.

264

265

Parameters:

266

- label: Display text for the option

267

- value: Associated value (defaults to None)

268

- description: Optional description shown on selection

269

- enabled: Whether option can be selected (default: True)

270

"""

271

272

label: str

273

value: Any

274

description: Optional[str]

275

enabled: bool

276

```

277

278

#### Option Usage Examples

279

280

```python

281

from pick import pick, Option

282

283

# Options with values and descriptions

284

options = [

285

Option("Python", ".py", "High-level, general-purpose programming language"),

286

Option("Java", ".java", "Class-based, object-oriented programming language"),

287

Option("JavaScript", ".js"),

288

Option("Disabled Option", enabled=False) # This option cannot be selected

289

]

290

291

option, index = pick(options, "Choose a language:")

292

print(f"Selected: {option.label} with value: {option.value}")

293

294

# Options with complex values

295

database_options = [

296

Option("PostgreSQL", {"driver": "psycopg2", "port": 5432}),

297

Option("MySQL", {"driver": "mysql", "port": 3306}),

298

Option("SQLite", {"driver": "sqlite3", "file": "db.sqlite"})

299

]

300

301

selected_db, _ = pick(database_options, "Choose database:")

302

config = selected_db.value

303

print(f"Using {selected_db.label} with config: {config}")

304

```

305

306

## Types

307

308

```python { .api }

309

from collections import namedtuple

310

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

311

312

Position = namedtuple('Position', ['y', 'x'])

313

"""Screen position coordinates as (y, x) tuple."""

314

315

OPTION_T = TypeVar("OPTION_T", str, Option)

316

"""Generic type variable for option types (string or Option objects)."""

317

318

PICK_RETURN_T = Tuple[OPTION_T, int]

319

"""Return type for pick results: (option, index) tuple."""

320

```

321

322

## Constants

323

324

```python { .api }

325

# Keyboard navigation key codes

326

KEYS_ENTER = (curses.KEY_ENTER, ord("\n"), ord("\r")) # Enter, Return, Keypad Enter

327

KEYS_UP = (curses.KEY_UP, ord("k")) # Up arrow, 'k'

328

KEYS_DOWN = (curses.KEY_DOWN, ord("j")) # Down arrow, 'j'

329

KEYS_SELECT = (curses.KEY_RIGHT, ord(" ")) # Right arrow, Space

330

331

# Multi-select symbols

332

SYMBOL_CIRCLE_FILLED = "(x)" # Selected indicator

333

SYMBOL_CIRCLE_EMPTY = "( )" # Unselected indicator

334

```

335

336

## Error Handling

337

338

The package raises `ValueError` exceptions in the following cases:

339

340

- **Empty options list**: When `options` parameter is empty

341

- **Invalid default_index**: When `default_index >= len(options)`

342

- **Invalid min_selection_count**: When `min_selection_count > len(options)` in multiselect mode

343

- **All options disabled**: When all `Option` objects have `enabled=False`

344

345

```python

346

from pick import pick, Option

347

348

try:

349

# This will raise ValueError

350

pick([], "Choose from nothing:")

351

except ValueError as e:

352

print(f"Error: {e}")

353

354

try:

355

# This will also raise ValueError

356

disabled_options = [

357

Option("Option 1", enabled=False),

358

Option("Option 2", enabled=False)

359

]

360

pick(disabled_options, "All disabled:")

361

except ValueError as e:

362

print(f"Error: {e}")

363

```

364

365

## Integration with Existing Curses Applications

366

367

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

368

369

```python

370

import curses

371

from pick import pick

372

373

def main(screen):

374

# Your existing curses setup

375

curses.curs_set(0)

376

377

# Use pick within your curses app

378

options = ['Option 1', 'Option 2', 'Option 3']

379

option, index = pick(

380

options,

381

"Choose an option:",

382

screen=screen,

383

position=(5, 10), # Position within your screen

384

clear_screen=False # Don't clear your existing content

385

)

386

387

# Continue with your curses application

388

screen.addstr(0, 0, f"You selected: {option}")

389

screen.refresh()

390

391

curses.wrapper(main)

392

```