Skip to content

Commit deb5c38

Browse files
authored
fix(cli): Colorize example sections in help output (#1008)
## Summary - Fix example section colorization in `tmuxp --help` output - Add "examples:" suffix to `build_description` headings for formatter detection - Add comprehensive tests for the fix ## Problem The `tmuxp --help` output wasn't highlighting example sections (like `load:`, `freeze:`, etc.) while `vcspull --help` correctly colored them. **Root cause:** The `build_description` function produced headings like `"load:"` instead of `"load examples:"`. The formatter's colorization logic uses `content_lower.endswith("examples:")` to detect section headings, so `"load:"` was never recognized. ## Solution Changed `build_description` to use `f"{heading} examples:"` format (matching vcspull): ```python # Before title = "examples:" if heading is None else f"{heading}:" # After title = "examples:" if heading is None else f"{heading} examples:" ```
2 parents 784f15c + 1f1d4c1 commit deb5c38

File tree

5 files changed

+126
-4
lines changed

5 files changed

+126
-4
lines changed

CHANGES

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force
3131

3232
<!-- To maintainers and contributors: Please add notes for the forthcoming version below -->
3333

34-
_Notes on the upcoming release will go here.__
34+
_Notes on the upcoming release will go here._
35+
36+
### Bug fixes
37+
38+
#### CLI example colorization (#1008)
39+
40+
- Fix example sections not being colorized in `tmuxp --help` output
41+
- Change `build_description` to use `"{heading} examples:"` format (e.g., "load examples:") for proper formatter detection
3542

3643
## tmuxp 1.63.0 (2026-01-11)
3744

src/tmuxp/_internal/colors.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,7 @@ def build_description(
850850
'My tool.\n\nexamples:\n mytool run'
851851
852852
>>> build_description("My tool.", [("sync", ["mytool sync repo"])])
853-
'My tool.\n\nsync:\n mytool sync repo'
853+
'My tool.\n\nsync examples:\n mytool sync repo'
854854
855855
>>> build_description("", [(None, ["cmd"])])
856856
'examples:\n cmd'
@@ -865,7 +865,7 @@ def build_description(
865865
for heading, commands in example_blocks:
866866
if not commands:
867867
continue
868-
title = "examples:" if heading is None else f"{heading}:"
868+
title = "examples:" if heading is None else f"{heading} examples:"
869869
lines = [title]
870870
lines.extend(f" {command}" for command in commands)
871871
sections.append("\n".join(lines))

tests/_internal/test_colors.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
ColorMode,
2222
Colors,
2323
UnknownStyleColor,
24+
build_description,
2425
get_color_mode,
2526
style,
2627
)
@@ -312,3 +313,50 @@ def test_heading_applies_bright_cyan_bold(monkeypatch: pytest.MonkeyPatch) -> No
312313
assert ANSI_BOLD in result
313314
assert "Local workspaces:" in result
314315
assert ANSI_RESET in result
316+
317+
318+
# build_description tests
319+
320+
321+
def test_build_description_named_heading_includes_examples_suffix() -> None:
322+
"""Named heading should include 'examples:' suffix for formatter detection."""
323+
result = build_description("My tool.", [("sync", ["mytool sync repo"])])
324+
325+
# Should be "sync examples:" not just "sync:"
326+
assert "sync examples:" in result
327+
# Verify the old format is not present (unless contained in "sync examples:")
328+
lines = result.split("\n")
329+
heading_line = next(line for line in lines if "sync" in line.lower())
330+
assert heading_line == "sync examples:"
331+
332+
333+
def test_build_description_no_heading_uses_examples() -> None:
334+
"""Heading=None should produce bare 'examples:' title."""
335+
result = build_description("My tool.", [(None, ["mytool run"])])
336+
337+
assert "examples:" in result
338+
assert result.count("examples:") == 1 # Just one
339+
340+
341+
def test_build_description_multiple_named_headings() -> None:
342+
"""Multiple named headings should all have 'examples:' suffix."""
343+
result = build_description(
344+
"My tool.",
345+
[
346+
("load", ["mytool load"]),
347+
("freeze", ["mytool freeze"]),
348+
("ls", ["mytool ls"]),
349+
],
350+
)
351+
352+
assert "load examples:" in result
353+
assert "freeze examples:" in result
354+
assert "ls examples:" in result
355+
356+
357+
def test_build_description_empty_intro() -> None:
358+
"""Empty intro should not add blank sections."""
359+
result = build_description("", [(None, ["cmd"])])
360+
361+
assert result.startswith("examples:")
362+
assert "cmd" in result

tests/cli/test_formatter.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,49 @@ def test_help_theme_from_colors_enabled_returns_colored(
216216
assert "\033[" in theme.prog
217217
assert "\033[" in theme.action
218218
assert theme.reset == ANSI_RESET
219+
220+
221+
def test_fill_text_section_heading_with_examples_suffix(
222+
monkeypatch: pytest.MonkeyPatch,
223+
) -> None:
224+
"""Section headings ending with 'examples:' are colorized without initial block."""
225+
monkeypatch.delenv("NO_COLOR", raising=False)
226+
colors = Colors(ColorMode.ALWAYS)
227+
formatter_cls = create_themed_formatter(colors)
228+
formatter = formatter_cls("tmuxp")
229+
230+
# This is what build_description produces - no initial "examples:" block
231+
text = (
232+
"load examples:\n tmuxp load myproject\n\n"
233+
"freeze examples:\n tmuxp freeze mysession"
234+
)
235+
result = formatter._fill_text(text, 80, "")
236+
237+
# Both headings should be colorized (contain ANSI escape codes)
238+
assert "\033[" in result
239+
# Commands should be colorized
240+
assert "tmuxp" in result
241+
242+
243+
def test_fill_text_multiple_example_sections_all_colorized(
244+
monkeypatch: pytest.MonkeyPatch,
245+
) -> None:
246+
"""Multiple 'X examples:' sections should all be colorized."""
247+
monkeypatch.delenv("NO_COLOR", raising=False)
248+
colors = Colors(ColorMode.ALWAYS)
249+
formatter_cls = create_themed_formatter(colors)
250+
formatter = formatter_cls("tmuxp")
251+
252+
text = """load examples:
253+
tmuxp load myproject
254+
255+
freeze examples:
256+
tmuxp freeze mysession
257+
258+
ls examples:
259+
tmuxp ls"""
260+
result = formatter._fill_text(text, 80, "")
261+
262+
# All sections should have ANSI codes
263+
# Count ANSI escape sequences - should have at least heading + command per section
264+
assert result.count("\033[") >= 6

tests/cli/test_help_examples.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def extract_examples_from_help(help_text: str) -> list[str]:
7171
for line in help_text.splitlines():
7272
# Match example section headings:
7373
# - "examples:" (default examples section)
74-
# - "load examples:" or "load:" (category headings)
74+
# - "load examples:" (category headings with examples suffix)
7575
# - "Field-scoped search:" (multi-word category headings)
7676
# Exclude argparse sections like "positional arguments:", "options:"
7777
stripped = line.strip()
@@ -250,3 +250,24 @@ def test_search_no_args_shows_help() -> None:
250250
assert "usage: tmuxp search" in result.stdout
251251
# Should exit successfully (not error)
252252
assert result.returncode == 0
253+
254+
255+
def test_main_help_example_sections_have_examples_suffix() -> None:
256+
"""Main --help should have section headings ending with 'examples:'."""
257+
help_text = _get_help_text()
258+
259+
# Should have "load examples:", "freeze examples:", etc.
260+
# NOT just "load:", "freeze:"
261+
assert "load examples:" in help_text.lower()
262+
assert "freeze examples:" in help_text.lower()
263+
264+
265+
def test_main_help_examples_are_colorized(monkeypatch: pytest.MonkeyPatch) -> None:
266+
"""Main --help should have colorized example sections when FORCE_COLOR is set."""
267+
monkeypatch.delenv("NO_COLOR", raising=False)
268+
monkeypatch.setenv("FORCE_COLOR", "1")
269+
270+
help_text = _get_help_text()
271+
272+
# Should contain ANSI escape codes for colorization
273+
assert "\033[" in help_text, "Example sections should be colorized"

0 commit comments

Comments
 (0)