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.06997% with 123 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.29%. Comparing base (3fb0680) to head (5983e01).

Files with missing lines Patch % Lines
src/tmuxp/cli/search.py 73.85% 58 Missing and 16 partials ⚠️
src/tmuxp/cli/ls.py 86.86% 13 Missing and 13 partials ⚠️
src/tmuxp/cli/load.py 71.42% 8 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 ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1006      +/-   ##
==========================================
+ Coverage   72.97%   76.29%   +3.31%     
==========================================
  Files          26       28       +2     
  Lines        1839     2417     +578     
  Branches      347      458     +111     
==========================================
+ Hits         1342     1844     +502     
- Misses        394      453      +59     
- Partials      103      120      +17     

☔ 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 added 16 commits January 3, 2026 13:02
Port vcspull's intuitive color practices into tmuxp CLI:

- Add _colors.py module with ColorMode enum and Colors class
- Add global --color flag (auto/always/never) to CLI
- Support NO_COLOR and FORCE_COLOR environment variables
- Update load.py with semantic colors (info, success, error, etc.)
- Update ls.py and debug_info.py to use Colors class

The Colors class wraps tmuxp's existing style() function with
semantic methods (success, warning, error, info, highlight, muted)
and handles TTY detection following CPython's _colorize patterns.
Add 25 tests covering:
- ColorMode detection (TTY, NO_COLOR, FORCE_COLOR)
- Semantic color methods (success, error, warning, etc.)
- get_color_mode() helper function
- Colors class behavior when disabled
Apply semantic color system to freeze command output:
- error() for exception messages (session not found)
- muted() for banner text and separators
- info() for URLs and file paths
- warning() for "file exists" messages
- success() + info() for "Saved to <path>" message

Add CLIFreezeNamespace.color field for color mode from global --color flag.
Includes 9 unit tests for color output formatting.
Apply semantic color system to import command output:
- error() for "Unknown config format" error
- muted() for separator and banner text
- info() for URLs and file paths
- success() + info() for "Saved to <path>" message

Update command_import_teamocil() and command_import_tmuxinator()
to accept color parameter from global --color flag.
Includes 9 unit tests for color output formatting.
Apply semantic color system to convert command output:
- highlight() for format type (json/yaml) in prompts
- info() for file paths in prompts
- success() + info() for "saved to <path>" message

Add color parameter to command_convert() from global --color flag.
Includes 7 unit tests for color output formatting.
Apply semantic color system to shell command output:
- muted() for static text ("Launching", "shell for session", "...")
- highlight(bold=False) for shell type (ipython, pdb, etc.)
- info() for session name

Add CLIShellNamespace.color field for color mode from global --color flag.
Note: 'colors' field (56/88 for tmux palette) is separate from 'color' (CLI output).
Includes 7 unit tests for color output formatting.
Apply semantic color system to edit command output:
- muted() for static text ("Opening", "in", "...")
- info() for workspace file path
- highlight(bold=False) for editor name

Add color parameter to command_edit() from global --color flag.
Includes 6 unit tests for color output formatting.
Add comprehensive integration tests verifying --color flag behavior:
- --color=auto respects TTY detection
- --color=always forces colors even without TTY
- --color=never disables colors even with TTY
- NO_COLOR environment variable overrides all modes
- FORCE_COLOR enables colors in auto mode without TTY
- NO_COLOR takes precedence over FORCE_COLOR
- All semantic methods respect enabled/disabled state
- get_color_mode() handles all edge cases

Includes 15 integration tests for cross-command color consistency.
Apply semantic color system to prompt utilities:
- prompt(): default value [path] uses info() (cyan)
- prompt_bool(): choice indicator [Y/n] uses muted() (blue)
- prompt_choices(): options list (a, b) uses muted(), default uses info()

Fix circular import between utils.py and _colors.py by using
lazy import of style() inside _colorize() method.

Includes 7 function-based tests for prompt color output.
Follow project conventions per AGENTS.md - use function-based tests
instead of class-based tests for color modules.

Converted files:
- test_colors.py
- test_cli_colors_integration.py
- test_convert_colors.py
- test_edit_colors.py
- test_freeze_colors.py
- test_import_colors.py
- test_shell_colors.py
Add PrivatePath class that collapses home directory to ~ in string
output, preventing PII exposure in logs and debug output.

- PrivatePath: pathlib.Path subclass with masked __str__/__repr__
- collapse_home_in_string(): Helper for PATH-like colon-separated strings
- Comprehensive tests for both utilities
Add reusable formatting methods to Colors class for syntax highlighting
in structured output like debug-info:

- format_label(): Bold magenta for key/label text
- format_path(): Cyan for file paths
- format_version(): Cyan for version strings
- format_separator(): Muted separator lines
- format_kv(): Key: value pairs with highlighted key
- format_tmux_option(): Handles "key value" and "key=value" formats
Apply semantic colors and privacy masking to debug-info output:

- Labels highlighted in bold magenta
- Paths and versions in cyan
- Separators in muted blue
- tmux options with key=value highlighting
- Home directory paths collapsed to ~ for privacy
- System PATH env var also privacy-masked
- Prioritize space-separated format to handle values containing '='
  (e.g., status-format[0] "#[align=left]")
- Add support for empty array options like 'pane-colours' (key only)
- Add test coverage for array-indexed options (status-format[0])
- Update docstring to document all supported formats
Per CLAUDE.md guidelines, use narrative descriptions for test
sections rather than inline comments.
Apply semantic colors to print statements that were missed during
the initial color system implementation.
@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 added 4 commits January 3, 2026 13:36
Per AGENTS.md guidelines, convert inline comments in doctests to
narrative descriptions outside the code blocks.
Consolidate all color/styling code in _colors.py to eliminate
circular dependency. This fixes doctest compatibility with
python -m doctest.

Moved: style(), unstyle(), strip_ansi(), _interpret_color(),
_ansi_colors, _ansi_reset_all, UnknownStyleColor
Only include stderr in output when it contains content, avoiding
extra blank lines in debug-info output.
Mask home directory in CLI path outputs for privacy. All commands
now display ~/... instead of /home/user/... when showing paths.

Updated: load, freeze, convert, edit, import_config
@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 18 commits January 4, 2026 11:59
- Add PrivatePath to load.py empty workspace warning
- Remove extra angle brackets from convert.py success message
  to match freeze.py style: "Saved to ~/.tmuxp/file.json."
- Update test to match new format
Add output formatting utilities for CLI commands supporting:
- HUMAN mode: Colored human-readable output (default)
- JSON mode: Buffered array output for machine parsing
- NDJSON mode: Streaming newline-delimited JSON

Provides get_output_mode() to determine mode from CLI flags.
Pattern based on vcspull's output module.
Enhance ls command with machine-readable output formats:
- --json: Output workspace list as JSON array
- --ndjson: Output as newline-delimited JSON (one per line)
- --tree: Group workspaces by directory with colored headers

Extended JSON metadata includes:
- name: workspace name (file stem)
- path: file path (with ~ contraction)
- format: yaml or json
- size: file size in bytes
- mtime: modification time (ISO format)
- session_name: from config if parseable

Tree mode shows session_name when it differs from filename.
Pattern based on vcspull list command.
Apply PrivatePath to all user-facing path displays in freeze command:
- Line 190: "Save to:" prompt now masks home directory
- Line 246: "Save to ...?" confirmation now masks home directory

Add tests for prompt masking behavior in test_freeze_colors.py.
Add find_local_workspace_files() function that searches for .tmuxp.yaml,
.tmuxp.yml, or .tmuxp.json files from a start directory upward to home.

Features:
- Traverses parent directories like git does for .git/
- Stops at user home directory by default (configurable)
- Returns files ordered from closest to farthest
- Prioritizes .yaml > .yml > .json when multiple exist

Add LOCAL_WORKSPACE_FILES constant for dotfile names.

Tests cover: upward traversal, multiple ancestors, format precedence,
edge cases (home dir, symlinks, filesystem root).
Add combined view of workspace files from both local directories
(cwd and parents) and global directory (~/.tmuxp/).

Changes:
- Add `source` field to WorkspaceInfo: "local" or "global"
- Import and use find_local_workspace_files() from finders
- Update _output_flat() to group workspaces by source with headers
- Local workspaces show path (useful since they may be in parent dirs)
- Global workspaces show only name (path is always ~/.tmuxp/)
- Update command_ls() to collect from both sources

Tests added:
- test_get_workspace_info_source_local
- test_ls_finds_local_workspace_in_cwd
- test_ls_finds_local_workspace_in_parent
- test_ls_shows_local_and_global
- test_ls_json_includes_source_for_local
- test_ls_local_shows_path

All existing tests updated with proper monkeypatching to avoid
picking up real .tmuxp.yaml from the project directory.
Add --full flag to tmuxp ls command for detailed workspace inspection.

Changes:
- Add --full argument to subparser
- Update _get_workspace_info() with include_config parameter
- Add _render_config_tree() helper for human-readable window/pane hierarchy
- Update _output_flat() and _output_tree() to display config tree when full=True
- JSON/NDJSON output with --full includes full parsed config object
- Human output with --full shows tree: windows with layout, panes with commands

Example human output with --full:
  dev
    ├── editor [main-horizontal]
    │   ├── pane 0: vim
    │   └── pane 1: git status
    └── shell
        └── pane 0

Example JSON with --full:
  {"name": "dev", ..., "config": {"session_name": "dev", "windows": [...]}}

Tests added:
- test_ls_full_flag_subparser
- test_get_workspace_info_include_config
- test_get_workspace_info_no_config_by_default
- test_ls_json_full_includes_config
- test_ls_full_tree_shows_windows
- test_ls_full_flat_shows_windows
- test_ls_full_without_json_no_config_in_output
Add foundation for tmuxp search command inspired by vcspull's search:

- SearchToken and SearchPattern types for query representation
- Field alias support (n:, s:, p:, w: shortcuts)
- Smart parsing: unknown prefixes treated as literal patterns
- Regex compilation with ignore_case, smart_case, fixed_strings, word_regexp
- InvalidFieldError for explicit --field validation
Add workspace search functionality:

- WorkspaceFields TypedDict: name, path, session_name, windows, panes
- WorkspaceSearchResult: filepath, source, fields, matches for output
- extract_workspace_fields: parses config for searchable content
- evaluate_match: AND/OR pattern matching with match tracking
- find_search_matches: coordinates search across workspace list

Match results include actual matched text for highlighting support.
Add search result output functionality:

- highlight_matches: regex-based highlighting with span merging
- _output_search_results: grouped output (local/global) with colors
- JSON output includes matched_fields and matches for scripting
- Human output shows session_name, windows, panes when matched
Complete CLI integration for tmuxp search command:

- SEARCH_DESCRIPTION with usage examples
- CLISearchNamespace for typed argument parsing
- create_search_subparser() with all search options
- command_search() entrypoint connecting all components
- Register in __init__.py with routing
- Add search to main CLI description examples

Supports: -i/-S (case), -F (fixed), -w (word), -v (invert),
--any (OR logic), --json/--ndjson output modes.
Add 59 tests covering all search functionality:

- normalize_fields: field aliases, case handling, validation
- parse_query_terms: prefixes, URLs, empty patterns
- compile_search_patterns: all matching modes (case, word, fixed, regex)
- extract_workspace_fields: config parsing, panes, windows
- evaluate_match: AND/OR logic, field searches
- find_search_matches: basic, invert, multiple workspaces
- highlight_matches: colors, multiple matches
- CLI subparser: options, output formats
- Output formatting: JSON, NDJSON, human readable
Update CLI_DESCRIPTION with new command options:

ls examples (from 57faf91, 67a4af2):
- --tree: grouped by directory
- --full: show window/pane details
- --json: machine-readable output

search examples:
- name:myproject: field-scoped search
- -i DEV: case-insensitive matching
- --json dev: machine-readable output
…ions

- Add 'search' to valid_subcommands in test_help_examples.py
- Add 'search' to parametrize decorator for subcommand tests
- Add test_search_subcommand_examples_are_valid test
- Fix mypy errors by explicitly typing WorkspaceSearchResult dicts
Use argparse set_defaults pattern to store print_help callable,
then invoke it when no query terms are provided. This makes
`tmuxp search` equivalent to `tmuxp search --help`.
Verify that `tmuxp search` with no arguments shows help output
and exits with code 0, equivalent to `tmuxp search --help`.
- Add --json flag to output structured JSON for machine parsing
- Use PrivatePath for all paths (home → ~) in JSON output
- Refactor to _collect_debug_info() and _format_human_output()
- Update help examples in debug-info and main CLI
- Add comprehensive tests with NamedTuple parametrization:
  - test_debug_info_output_modes (parametrized human/JSON)
  - test_debug_info_json_output (structure validation)
  - test_debug_info_json_no_ansi (no ANSI codes in JSON)
  - test_debug_info_json_paths_use_private_path (privacy)
tony added 11 commits January 4, 2026 12:03
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
Add explicit mentions of `jq` piping and automation use cases for all
JSON output features (search, ls, debug-info) to improve discoverability.
…ce dirs

- Add heading() method to Colors class for section headers (cyan+bold)
- Use highlight() for workspace names (magenta+bold) to distinguish from headers
- Add get_workspace_dir_candidates() to show all checked directories
- Display active directory in header: "Global workspaces (~/.tmuxp):"
- Show candidate directories with source labels (Legacy, XDG, etc.)
- Add comprehensive tests for new functionality

Color hierarchy:
- L0 Headers: heading() cyan+bold
- L1 Items: highlight() magenta+bold
- L2 Paths: info() cyan
- L3 Labels: muted() blue
Document the semantic color system for CLI output:
- Hierarchy-based color assignment (L0-L3 levels)
- Status-based color overrides
- Design principles from jq, ripgrep, mise/just
- Available Colors class methods
- Key rule: never use same color for adjacent hierarchy levels
Consistency with ls.py - section headers should use heading()
(cyan+bold) instead of muted() (blue dim) per Color Semantics Rev 1.
Replace inline separator strings with colors.format_separator()
for consistency with debug-info and other CLI output patterns.

Files: freeze.py, import_config.py
The prompt() function now masks home directory in the displayed
default value using PrivatePath, showing ~ instead of /home/user.

Before: Save to: ~/.tmuxp/session.yaml [/home/user/.tmuxp/session.yaml]
After:  Save to: ~/.tmuxp/session.yaml [~/.tmuxp/session.yaml]

The actual returned value remains the full path for file operations.
Previously, `tmuxp search gp-libs` would not find workspaces with a
window named "gp-libs" because window/pane fields required explicit
`window:` or `pane:` prefixes.

Now searches all fields by default:
- name (workspace filename)
- session_name
- path
- window (window names)
- pane (pane shell commands)
- Add create_themed_formatter() factory to inject theme into argparse formatters
- Help output now colorizes examples when FORCE_COLOR=1 or on TTY
- Respects NO_COLOR environment variable per no-color.org standard
- Add _theme class attribute to TmuxpHelpFormatter for proper typing

Also fixes:
- Remove forbidden # doctest: +SKIP from _output.py (AGENTS.md violation)
- Update search.py doctests for new DEFAULT_FIELDS with window/pane
- Add missing pathlib import to test_prompt_colors.py

Tests: Add tests/cli/test_formatter.py with 12 tests for factory and colorization
… codes

rstrip("\033[0m") strips individual characters (\, 0, 3, [, m), not
the string as a suffix. This corrupted ANSI sequences, leaving broken
codes like '\x1b[1' instead of '\x1b[1m'.

removesuffix() correctly removes the exact suffix string.
PrivatePath("") returns "." (current directory), which would show
"shell: ." in debug-info output when SHELL env var is unset/empty.

Now empty strings return "" like None does.
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