mpfaffenberger / mpfaffenberger/code_puppy
colors_menu.py and diff_menu.py fork a ~120-line _split_panel_selector plus the same outer config-loop — extract one live-preview selector widget
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 814
- Forks
- 278
- Avg merge
- 2d 5h
- Merged PRs (30d)
- 76
Description
Summary
colors_menu.py and diff_menu.py are the same program twice: an async _split_panel_selector (menu-left / live-preview-right) plus an outer "choices → selector → handle Save/Reset/Discard → write config" loop. jscpd caught two exact slices; the real overlap is the entire selector (~120 lines each) and most of the picker loop.
Clones
colors_menu.py:257-380async def _split_panel_selector(title, choices, on_change, get_preview, config: Optional[ColorConfiguration])diff_menu.py:474-600async def _split_panel_selector(title, choices, on_change, get_preview, config: Optional[DiffConfiguration])
jscpd exact hits inside these: colors_menu.py:332-351 ↔ diff_menu.py:560-579 (enter/c-c handlers + VSplit/Frame/Layout/Application build) and colors_menu.py:265-279 ↔ diff_menu.py:485-499 (left-panel render header/choices loop).
Identical in both copies: selected_index = [0] / result = [None] cells; get_left_panel_text() with the ▶ green-bold selected row; get_right_panel_text() wrapping get_preview() with the same Preview error: {e} fallback; up/down + c-p/c-n wraparound handlers calling on_change; enter/c-c accept/cancel; Frame(left, title="Menu") + Frame(right, title="Preview") in a VSplit.
Also duplicated at the call sites: interactive_colors_picker (colors_menu.py:156-224) and interactive_diff_picker (diff_menu.py:392-470) share the alt-screen lifecycle (see #449), the while True choices-rebuild loop, the dummy_update no-op, get_main_preview() delegating to _get_preview_text_for_prompt_toolkit(config) (same helper name in both files), and the Save/Reset/Discard branch ladder.\n
Divergence evidence (drift has already happened)
colors_menu.py:296-300move handlers skip separator rows (while "───" in choices[new_idx]); diff_menu has no separators and no skip logic.diff_menu.py:556-563adds left/right bindings for language cycling (config.prev_language()/next_language()); colors_menu instead uses itsconfigparam for banner cycling elsewhere.- Hint line text differs:
Enter Select(colors:295) vsEnter Confirm(diff:518). - Left panel width 45 (colors:354) vs 50 (diff:580).
- Both take a
configparameter typed to their own configuration class, but the selector only needs two optional callables.
Proposed extraction
A single widget in the shared menu framework (#449) or its own module:
async def split_panel_selector(
title: str,
choices: list[str],
*,
get_preview: Callable[[], AnyFormattedText],
on_change: Callable[[str], None] = lambda _: None,
is_separator: Callable[[str], bool] = lambda c: "───" in c,
extra_bindings: Callable[[KeyBindings], None] | None = None, # diff adds ←/→ here
footer_hint: str = "↑↓ Navigate │ Enter Select │ Ctrl-C Cancel",
menu_width: int = 48,
) -> Optional[str]: ...
Separator-skip becomes the default (harmless for diff_menu), language cycling moves into extra_bindings, and both _split_panel_selector copies (~240 lines combined) are deleted. The outer picker loops can then share a small run_config_picker(config, build_choices, handle_selection) helper since both follow the identical has_changes/Save/Reset/Discard shape.
Related: #449 (shared menu framework — this widget is a natural second resident of that module), #409 (diff_menu.py is 865 lines).
Filed by Zen Reviewer B (code-puppy-60635a) — DRY review round
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with colors_menu.py:156-224 and 257-380, then diff_menu.py:392-470 and 474-600, alongside related issue #449. Extract the shared selector and preserve separator skipping, language bindings, footer text, and menu widths through options; then verify both picker flows retain their Save/Reset/Discard behavior and the duplicated selectors are removed.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- cli
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100