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

Open
#451 0 comments 0 reactions 0 assignees View on GitHub

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-380 async def _split_panel_selector(title, choices, on_change, get_preview, config: Optional[ColorConfiguration])
  • diff_menu.py:474-600 async def _split_panel_selector(title, choices, on_change, get_preview, config: Optional[DiffConfiguration])

jscpd exact hits inside these: colors_menu.py:332-351diff_menu.py:560-579 (enter/c-c handlers + VSplit/Frame/Layout/Application build) and colors_menu.py:265-279diff_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-300 move handlers skip separator rows (while "───" in choices[new_idx]); diff_menu has no separators and no skip logic.
  • diff_menu.py:556-563 adds left/right bindings for language cycling (config.prev_language() / next_language()); colors_menu instead uses its config param for banner cycling elsewhere.
  • Hint line text differs: Enter Select (colors:295) vs Enter Confirm (diff:518).
  • Left panel width 45 (colors:354) vs 50 (diff:580).
  • Both take a config parameter 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.