Skip to content

Commit ac2466c

Browse files
nicksenapclaude
andcommitted
Fix terminal menu crash when search text exceeds terminal width
Extract _make_menu and _show_menu helpers to DRY up TerminalMenu construction. Catch (ValueError, OSError) from simple_term_menu's search rendering bug and gracefully fall back to /-triggered search. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ca1bf52 commit ac2466c

3 files changed

Lines changed: 47 additions & 29 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "grove"
3-
version = "0.11.0"
3+
version = "0.11.1"
44
description = "Git Worktree Workspace Orchestrator"
55
readme = "README.md"
66
authors = [

src/grove/cli.py

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -135,23 +135,52 @@ def _pick_one(prompt_text: str, choices: list[str]) -> str:
135135
return choices[_pick_one_idx(prompt_text, choices)]
136136

137137

138+
def _make_menu(
139+
choices: list[str],
140+
title: str,
141+
*,
142+
multi_select: bool = False,
143+
type_to_search: bool = True,
144+
):
145+
from simple_term_menu import TerminalMenu
146+
147+
kwargs: dict = {
148+
"menu_cursor": "❯ ",
149+
"menu_cursor_style": ("fg_cyan", "bold"),
150+
"menu_highlight_style": ("fg_cyan", "bold"),
151+
"search_highlight_style": ("fg_yellow", "bold"),
152+
}
153+
if multi_select:
154+
kwargs.update(
155+
multi_select=True,
156+
multi_select_select_on_accept=False,
157+
multi_select_keys=("tab",),
158+
)
159+
if type_to_search:
160+
kwargs["search_key"] = None
161+
return TerminalMenu(choices, title=title, **kwargs)
162+
163+
164+
def _show_menu(menu, choices: list[str], fallback_title: str, **kw):
165+
"""Show a menu, falling back to /-triggered search if the library crashes."""
166+
try:
167+
return menu.show()
168+
except (ValueError, OSError):
169+
# simple_term_menu can crash when search text exceeds terminal width;
170+
# recover by retrying without live search
171+
fallback = _make_menu(choices, fallback_title, type_to_search=False, **kw)
172+
return fallback.show()
173+
174+
138175
def _pick_one_idx(prompt_text: str, choices: list[str]) -> int:
139176
"""Arrow-key single selection with type-to-search, returns the chosen index."""
140177
if not sys.stdin.isatty():
141178
error("Interactive selection requires a terminal. Provide explicit flags instead.")
142179
raise typer.Exit(1)
143-
from simple_term_menu import TerminalMenu
144-
145-
menu = TerminalMenu(
146-
choices,
147-
title=f"\n{prompt_text}\n ↑/↓ navigate · type to search · enter confirm",
148-
menu_cursor="❯ ",
149-
menu_cursor_style=("fg_cyan", "bold"),
150-
menu_highlight_style=("fg_cyan", "bold"),
151-
search_highlight_style=("fg_yellow", "bold"),
152-
search_key=None,
153-
)
154-
idx = menu.show()
180+
title = f"\n{prompt_text}\n ↑/↓ navigate · type to search · enter confirm"
181+
fallback_title = f"\n{prompt_text}\n ↑/↓ navigate · / to search · enter confirm"
182+
menu = _make_menu(choices, title)
183+
idx = _show_menu(menu, choices, fallback_title)
155184
if idx is None:
156185
raise typer.Abort()
157186
return idx
@@ -162,22 +191,11 @@ def _pick_many(prompt_text: str, choices: list[str]) -> list[str]:
162191
if not sys.stdin.isatty():
163192
error("Interactive selection requires a terminal. Provide explicit flags instead.")
164193
raise typer.Exit(1)
165-
from simple_term_menu import TerminalMenu
166-
167194
display = ["(all)", *choices]
168-
menu = TerminalMenu(
169-
display,
170-
title=f"\n{prompt_text}\n ↑/↓ navigate · tab select · type to search · enter confirm",
171-
multi_select=True,
172-
multi_select_select_on_accept=False,
173-
multi_select_keys=("tab",),
174-
menu_cursor="❯ ",
175-
menu_cursor_style=("fg_cyan", "bold"),
176-
menu_highlight_style=("fg_cyan", "bold"),
177-
search_highlight_style=("fg_yellow", "bold"),
178-
search_key=None,
179-
)
180-
result = menu.show()
195+
title = f"\n{prompt_text}\n ↑/↓ navigate · tab select · type to search · enter confirm"
196+
fallback_title = f"\n{prompt_text}\n ↑/↓ navigate · tab select · / to search · enter confirm"
197+
menu = _make_menu(display, title, multi_select=True)
198+
result = _show_menu(menu, display, fallback_title, multi_select=True)
181199
if result is None:
182200
raise typer.Abort()
183201
selected = menu.chosen_menu_entries

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)