Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force

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

_Notes on the upcoming release will go here.__
_Notes on the upcoming release will go here._

### Bug fixes

#### CLI example colorization (#1008)

- Fix example sections not being colorized in `tmuxp --help` output
- Change `build_description` to use `"{heading} examples:"` format (e.g., "load examples:") for proper formatter detection

## tmuxp 1.63.0 (2026-01-11)

Expand Down
4 changes: 2 additions & 2 deletions src/tmuxp/_internal/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -850,7 +850,7 @@ def build_description(
'My tool.\n\nexamples:\n mytool run'

>>> build_description("My tool.", [("sync", ["mytool sync repo"])])
'My tool.\n\nsync:\n mytool sync repo'
'My tool.\n\nsync examples:\n mytool sync repo'

>>> build_description("", [(None, ["cmd"])])
'examples:\n cmd'
Expand All @@ -865,7 +865,7 @@ def build_description(
for heading, commands in example_blocks:
if not commands:
continue
title = "examples:" if heading is None else f"{heading}:"
title = "examples:" if heading is None else f"{heading} examples:"
lines = [title]
lines.extend(f" {command}" for command in commands)
sections.append("\n".join(lines))
Expand Down
48 changes: 48 additions & 0 deletions tests/_internal/test_colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
ColorMode,
Colors,
UnknownStyleColor,
build_description,
get_color_mode,
style,
)
Expand Down Expand Up @@ -312,3 +313,50 @@ def test_heading_applies_bright_cyan_bold(monkeypatch: pytest.MonkeyPatch) -> No
assert ANSI_BOLD in result
assert "Local workspaces:" in result
assert ANSI_RESET in result


# build_description tests


def test_build_description_named_heading_includes_examples_suffix() -> None:
"""Named heading should include 'examples:' suffix for formatter detection."""
result = build_description("My tool.", [("sync", ["mytool sync repo"])])

# Should be "sync examples:" not just "sync:"
assert "sync examples:" in result
# Verify the old format is not present (unless contained in "sync examples:")
lines = result.split("\n")
heading_line = next(line for line in lines if "sync" in line.lower())
assert heading_line == "sync examples:"


def test_build_description_no_heading_uses_examples() -> None:
"""Heading=None should produce bare 'examples:' title."""
result = build_description("My tool.", [(None, ["mytool run"])])

assert "examples:" in result
assert result.count("examples:") == 1 # Just one


def test_build_description_multiple_named_headings() -> None:
"""Multiple named headings should all have 'examples:' suffix."""
result = build_description(
"My tool.",
[
("load", ["mytool load"]),
("freeze", ["mytool freeze"]),
("ls", ["mytool ls"]),
],
)

assert "load examples:" in result
assert "freeze examples:" in result
assert "ls examples:" in result


def test_build_description_empty_intro() -> None:
"""Empty intro should not add blank sections."""
result = build_description("", [(None, ["cmd"])])

assert result.startswith("examples:")
assert "cmd" in result
46 changes: 46 additions & 0 deletions tests/cli/test_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,49 @@ def test_help_theme_from_colors_enabled_returns_colored(
assert "\033[" in theme.prog
assert "\033[" in theme.action
assert theme.reset == ANSI_RESET


def test_fill_text_section_heading_with_examples_suffix(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Section headings ending with 'examples:' are colorized without initial block."""
monkeypatch.delenv("NO_COLOR", raising=False)
colors = Colors(ColorMode.ALWAYS)
formatter_cls = create_themed_formatter(colors)
formatter = formatter_cls("tmuxp")

# This is what build_description produces - no initial "examples:" block
text = (
"load examples:\n tmuxp load myproject\n\n"
"freeze examples:\n tmuxp freeze mysession"
)
result = formatter._fill_text(text, 80, "")

# Both headings should be colorized (contain ANSI escape codes)
assert "\033[" in result
# Commands should be colorized
assert "tmuxp" in result


def test_fill_text_multiple_example_sections_all_colorized(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Multiple 'X examples:' sections should all be colorized."""
monkeypatch.delenv("NO_COLOR", raising=False)
colors = Colors(ColorMode.ALWAYS)
formatter_cls = create_themed_formatter(colors)
formatter = formatter_cls("tmuxp")

text = """load examples:
tmuxp load myproject

freeze examples:
tmuxp freeze mysession

ls examples:
tmuxp ls"""
result = formatter._fill_text(text, 80, "")

# All sections should have ANSI codes
# Count ANSI escape sequences - should have at least heading + command per section
assert result.count("\033[") >= 6
23 changes: 22 additions & 1 deletion tests/cli/test_help_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def extract_examples_from_help(help_text: str) -> list[str]:
for line in help_text.splitlines():
# Match example section headings:
# - "examples:" (default examples section)
# - "load examples:" or "load:" (category headings)
# - "load examples:" (category headings with examples suffix)
# - "Field-scoped search:" (multi-word category headings)
# Exclude argparse sections like "positional arguments:", "options:"
stripped = line.strip()
Expand Down Expand Up @@ -250,3 +250,24 @@ def test_search_no_args_shows_help() -> None:
assert "usage: tmuxp search" in result.stdout
# Should exit successfully (not error)
assert result.returncode == 0


def test_main_help_example_sections_have_examples_suffix() -> None:
"""Main --help should have section headings ending with 'examples:'."""
help_text = _get_help_text()

# Should have "load examples:", "freeze examples:", etc.
# NOT just "load:", "freeze:"
assert "load examples:" in help_text.lower()
assert "freeze examples:" in help_text.lower()


def test_main_help_examples_are_colorized(monkeypatch: pytest.MonkeyPatch) -> None:
"""Main --help should have colorized example sections when FORCE_COLOR is set."""
monkeypatch.delenv("NO_COLOR", raising=False)
monkeypatch.setenv("FORCE_COLOR", "1")

help_text = _get_help_text()

# Should contain ANSI escape codes for colorization
assert "\033[" in help_text, "Example sections should be colorized"