Skip to content

Conversation

@tony
Copy link
Member

@tony tony commented Jan 3, 2026

Summary

Port vcspull's intuitive color practices into tmuxp CLI, incorporating CPython best practices.

  • Add _colors.py module with ColorMode enum and Colors class
  • Add global --color flag (auto/always/never) to CLI root parser
  • Support NO_COLOR and FORCE_COLOR environment variables (CPython-style)
  • Update load.py with semantic colors (info, success, error, highlight, etc.)
  • Update ls.py and debug_info.py to use Colors class
  • Add comprehensive test suite (25 tests) for color system

Design

The Colors class wraps tmuxp's existing style() function with semantic methods:

  • success() - green, for successful operations
  • warning() - yellow, for warnings
  • error() - red, for errors
  • info() - cyan, for informational messages
  • highlight() - magenta (bold), for important text
  • muted() - blue, for secondary text

Usage

# Automatic color detection (default)
tmuxp load myworkspace

# Force colors on (useful for piping to less -R)
tmuxp --color=always load myworkspace

# Disable colors
tmuxp --color=never load myworkspace

# Environment variables also work
NO_COLOR=1 tmuxp load myworkspace

Test plan

  • All 25 new color tests pass
  • All 81 existing CLI tests pass
  • ruff checks pass
  • mypy type checking passes
  • Doctests pass

@tony tony force-pushed the cli-colors branch 2 times, most recently from 24caeb3 to 05ee4e3 Compare January 3, 2026 17:43
@codecov
Copy link

codecov bot commented Jan 3, 2026

Codecov Report

❌ Patch coverage is 82.74174% with 141 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.11%. Comparing base (df78a17) to head (8b41806).
⚠️ Report is 100 commits behind head on master.

Files with missing lines Patch % Lines
src/tmuxp/cli/search.py 74.03% 58 Missing and 16 partials ⚠️
src/tmuxp/_internal/colors.py 84.00% 13 Missing and 11 partials ⚠️
src/tmuxp/cli/ls.py 90.50% 10 Missing and 7 partials ⚠️
src/tmuxp/cli/load.py 72.72% 9 Missing ⚠️
src/tmuxp/cli/import_config.py 71.42% 2 Missing and 2 partials ⚠️
src/tmuxp/cli/debug_info.py 92.68% 2 Missing and 1 partial ⚠️
src/tmuxp/cli/edit.py 50.00% 3 Missing ⚠️
src/tmuxp/_internal/private_path.py 93.54% 1 Missing and 1 partial ⚠️
src/tmuxp/cli/freeze.py 80.00% 1 Missing and 1 partial ⚠️
src/tmuxp/cli/shell.py 87.50% 1 Missing ⚠️
... and 2 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1006      +/-   ##
==========================================
+ Coverage   77.35%   80.11%   +2.76%     
==========================================
  Files          25       28       +3     
  Lines        1722     2409     +687     
  Branches      328      457     +129     
==========================================
+ Hits         1332     1930     +598     
- Misses        287      356      +69     
- Partials      103      123      +20     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tony tony force-pushed the cli-colors branch 2 times, most recently from 604b533 to 53654da Compare January 3, 2026 17:54
@tony
Copy link
Member Author

tony commented Jan 3, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

@tony
Copy link
Member Author

tony commented Jan 3, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

@tony
Copy link
Member Author

tony commented Jan 3, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@tony tony changed the title feat(cli): Add semantic color system with --color flag CLI Colors Jan 4, 2026
tony added a commit that referenced this pull request Jan 4, 2026
tony added a commit that referenced this pull request Jan 4, 2026
tony added a commit that referenced this pull request Jan 4, 2026
tony added a commit that referenced this pull request Jan 4, 2026
Document new features added to CLI Colors PR #1006:
- New tmuxp search command with field-scoped search
- Enhanced tmuxp ls with --tree, --full, --json, --ndjson
- Local workspace discovery from cwd and parents
- tmuxp debug-info --json for machine-readable output
tony added a commit that referenced this pull request Jan 10, 2026
tony added a commit that referenced this pull request Jan 10, 2026
Document new features added to CLI Colors PR #1006:
- New tmuxp search command with field-scoped search
- Enhanced tmuxp ls with --tree, --full, --json, --ndjson
- Local workspace discovery from cwd and parents
- tmuxp debug-info --json for machine-readable output
@tony
Copy link
Member Author

tony commented Jan 10, 2026

Code review

Found 1 issue:

  1. Infinite loop in prompt_choices() on invalid input - The function has a while True: loop (lines 229-238) but no handling when user input doesn't match any valid choice. When input is: (1) not empty, (2) not the default, (3) not in no_choice, and (4) not in choices_, the loop continues silently forever without feedback or re-prompting.

https://github.com/tmux-python/tmuxp/blob/a1ddbd3494131b4bdeebf2e072e1223a3af249a9/src/tmuxp/cli/utils.py#L228-L239

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@tony
Copy link
Member Author

tony commented Jan 10, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

1 similar comment
@tony
Copy link
Member Author

tony commented Jan 10, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

@tony
Copy link
Member Author

tony commented Jan 10, 2026

Code review

Found 1 issue:

  1. Functions _render_global_workspace_dirs(), _output_flat(), and _output_tree() in ls.py have no doctest examples. CLAUDE.md says "All functions and methods MUST have working doctests."

tmuxp/src/tmuxp/cli/ls.py

Lines 333 to 372 in 72b76f6

def _render_global_workspace_dirs(
formatter: OutputFormatter,
colors: Colors,
global_dir_candidates: list[dict[str, t.Any]],
) -> None:
"""Render global workspace directories section.
Parameters
----------
formatter : OutputFormatter
Output formatter.
colors : Colors
Color manager.
global_dir_candidates : list[dict[str, Any]]
List of global workspace directory candidates with metadata.
"""
formatter.emit_text("")
formatter.emit_text(colors.heading("Global workspace directories:"))
for candidate in global_dir_candidates:
path = candidate["path"]
source = candidate.get("source", "")
source_prefix = f"{source}: " if source else ""
if candidate["exists"]:
count = candidate["workspace_count"]
status = f"{count} workspace{'s' if count != 1 else ''}"
if candidate["active"]:
status += ", active"
formatter.emit_text(
f" {colors.muted(source_prefix)}{colors.info(path)} "
f"({colors.success(status)})"
)
else:
formatter.emit_text(
f" {colors.muted(source_prefix)}{colors.info(path)} ({status})"
)
else:
formatter.emit_text(
f" {colors.muted(source_prefix)}{colors.info(path)} "
f"({colors.muted('not found')})"
)

https://github.com/tmux-python/tmuxp/blob/72b76f632d1bea23142da1c1d55ee44f532bc13e/CLAUDE.md#L99-L101

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@tony
Copy link
Member Author

tony commented Jan 10, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

@tony
Copy link
Member Author

tony commented Jan 10, 2026

Code review (fresh)

No issues found. Checked for bugs and AGENTS.md compliance.

Notes: Several potential issues were identified but scored below threshold:

  • Missing newline in .tmuxp.json (linter should catch)
  • heading()/info() use same color (design choice, not code bug)
  • workspace→CLI import dependency (architectural, not a bug)

🤖 Generated with Claude Code

@tony
Copy link
Member Author

tony commented Jan 10, 2026

Code Review Summary

Review completed - 5 parallel agents examined:

  1. CLAUDE.md/AGENTS.md compliance
  2. Shallow bug scanning
  3. Git history analysis
  4. Previous PRs patterns
  5. Code comment analysis

Issues Analyzed

Issue Score Verdict
Functional tests guideline contradiction 5/100 ❌ False positive - tests are already function-based (139 functions, 0 classes)
args=None AttributeError in debug_info.py 15/100 ❌ False positive - code safely uses args.color if args else None
RGB validation missing in style() 72/100 ⚠️ Valid but low priority - terminals handle gracefully
ls output format breaking change 62/100 ⚠️ Documented in CHANGES, JSON changed from array to object
style() bold=False behavior change 25/100 ❌ False positive - actually a bug fix (code 22 interfered with dim)

Result

No blocking issues found. All scored below 80 threshold.

The PR is well-structured with proper test coverage and documentation. The output format changes are disclosed in CHANGES.

tony added a commit that referenced this pull request Jan 10, 2026
tony added a commit that referenced this pull request Jan 10, 2026
Document new features added to CLI Colors PR #1006:
- New tmuxp search command with field-scoped search
- Enhanced tmuxp ls with --tree, --full, --json, --ndjson
- Local workspace discovery from cwd and parents
- tmuxp debug-info --json for machine-readable output
tony added 7 commits January 11, 2026 04:42
why: Color tests now belong in _internal/ since the module moved there.

what:
- Move test_colors.py → tests/_internal/test_colors.py
- Move test_colors_formatters.py → tests/_internal/test_colors_formatters.py
- Move test_cli_colors_integration.py → tests/_internal/test_colors_integration.py
- Create tests/_internal/conftest.py with ANSI constants and color fixtures
- Update imports to use tmuxp._internal.colors
why: tmuxp_echo was in cli/utils.py but used by workspace/finders.py,
creating a reverse dependency (workspace→cli). Moving it to log.py puts
it in the right architectural layer alongside other logging utilities.

what:
- Add tmuxp_echo function to src/tmuxp/log.py
- Re-export from cli/utils.py for backward compatibility
- Update workspace/finders.py to import from tmuxp.log
- Fix load.py to set log level on tmuxp root logger for --log-file
why: The original type `list[str] | tuple[str, str]` was semantically wrong.
`tuple[str, str]` means exactly 2 strings, not a sequence of choice items.

what:
- Change type from `list[str] | tuple[str, str]` to `Sequence[str | tuple[str, str]]`
- This correctly describes: a sequence of items where each is str or (key, value) tuple
- Update docstring to clarify parameter semantics
why: Both heading() and info() used cyan color, violating the rule that
adjacent hierarchy levels should have visually distinct colors. Bold alone
may not be distinguishable on some terminal/font combinations.

what:
- Add HEADING constant with value "bright_cyan"
- Update heading() to use HEADING instead of INFO
- Bright cyan is visually distinct from regular cyan while maintaining
  the color family cohesion for information-related text
Validate that RGB tuple values are:
- Integers (raises TypeError -> UnknownStyleColor if not)
- In the 0-255 range (raises ValueError -> UnknownStyleColor if not)

Previously, invalid values like (256, 0, 0) or (-1, 128, 0) would
produce malformed ANSI escape codes that violate the standard.
The jq example `.[] | .name` doesn't work with the JSON output format
`{"workspaces": [...]}`. Fix to `.workspaces[].name`.
tony added 14 commits January 11, 2026 04:43
The argparse directive already outputs the command description from
the Python docstring, so having it manually repeated in the .md file
creates duplication. Remove the redundant text.
Drop the trailing colon from example category names in help text
(e.g., "Machine-readable output:" → "Machine-readable output").
Cleaner formatting for sphinx-argparse documentation output.
Category headings now use clean format without "examples:" suffix:

Before: "Field-scoped search examples:"
After:  "Field-scoped search:"

- build_description() formats headings as "{heading}:" not "{heading} examples:"
- Formatter recognizes category headings within examples blocks
- Add tests for category heading colorization
- Update extract_examples_from_help() to match new format
Per AGENTS.md guidelines: "Write tests as standalone functions, not
classes. Avoid class TestFoo: groupings."

Converted 24 test classes across 4 files:
- test_formatter.py: 4 classes → 14 functions
- test_output.py: 7 classes → 23 functions
- test_ls.py: 6 classes → 32 functions
- test_search.py: 7 classes → 40 functions (keeping parameterized tests)

All 303 CLI tests pass.
Replace bare `except Exception` with specific exception types:
- First try block: `except AttributeError` for string operations on
  non-string plugin values
- Second try block: `except (ImportError, AttributeError)` for module
  import failures and missing plugin class attributes

This prevents catching unrelated errors like KeyboardInterrupt or
MemoryError that should propagate up.
Replace bare `except Exception` with `except (yaml.YAMLError,
json.JSONDecodeError, OSError)` in `_get_workspace_info()`.

This ensures only expected errors from config file parsing are caught,
allowing unrelated errors to propagate for proper debugging.
Replace bare `except Exception` with `except (yaml.YAMLError,
json.JSONDecodeError, OSError)` in `extract_workspace_fields()`.

This ensures only expected errors from config file parsing are caught,
allowing unrelated errors to propagate for proper debugging.
Add `monkeypatch.delenv("TMUXP_CONFIGDIR", raising=False)` to prevent
test pollution from user environment. The fixture now properly isolates
tests from all workspace directory configuration sources.
Verify that heading() applies bright_cyan (ANSI 96) with bold when
colors are enabled. Previously only the disabled case was tested.

Also adds ANSI_BRIGHT_CYAN constant to conftest.py for test assertions.
Add test_compile_search_patterns_invalid_regex_raises to verify that
compile_search_patterns() raises re.error when given an invalid regex
pattern like "[invalid(".

This ensures the re.error exception handling path in command_search()
is properly tested.
@tony
Copy link
Member Author

tony commented Jan 11, 2026

Code review

Found 1 issue:

  1. Inconsistent color semantics in search.py vs ls.py (CLAUDE.md says "L1 Primary content = highlight() for Workspace names" and "L2 Supplementary info = info() for Paths")

In search.py, workspace names use colors.info() and paths use colors.muted(), but ls.py correctly uses colors.highlight() for names and colors.info() for paths per the documented hierarchy.

name_display = highlight_matches(fields["name"], patterns, colors=colors)
path_info = f" {colors.muted(fields['path'])}" if show_path else ""
formatter.emit_text(f" {colors.info(name_display)}{path_info}")

Compare with the correct implementation in ls.py:

tmuxp/src/tmuxp/cli/ls.py

Lines 438 to 441 in 945a6cb

formatter.emit(ws)
path_info = f" {colors.info(ws['path'])}" if show_path else ""
formatter.emit_text(f" {colors.highlight(ws['name'])}{path_info}")

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Change search output to use highlight() for workspace names (L1 primary
content) and info() for paths (L2 supplementary info), matching the
pattern used in ls.py per CLAUDE.md CLI Color Semantics.

Before: names used info() (cyan), paths used muted() (blue)
After: names use highlight() (magenta+bold), paths use info() (cyan)
@tony tony merged commit 9c921dd into master Jan 11, 2026
14 checks passed
@tony tony deleted the cli-colors branch January 11, 2026 11:39
tony added a commit that referenced this pull request Jan 11, 2026
tony added a commit that referenced this pull request Jan 11, 2026
tony added a commit that referenced this pull request Jan 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants