Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
75fed65
feat(cli): Add semantic color system with --color flag
tony Jan 3, 2026
63fda2b
tests(cli): Add comprehensive tests for color system
tony Jan 3, 2026
bb681ce
feat(cli/freeze): Add semantic colors to freeze command
tony Jan 3, 2026
9bf4ebd
feat(cli/import): Add semantic colors to import command
tony Jan 3, 2026
b8b14b2
feat(cli/convert): Add semantic colors to convert command
tony Jan 3, 2026
e380f12
feat(cli/shell): Add semantic colors to shell command
tony Jan 3, 2026
2e3b3eb
feat(cli/edit): Add semantic colors to edit command
tony Jan 3, 2026
5150dce
tests(cli): Add integration tests for color output
tony Jan 3, 2026
0bde700
feat(cli/utils): Add semantic colors to interactive prompts
tony Jan 3, 2026
521d4ca
tests(cli[colors]): Convert class-based tests to functions
tony Jan 3, 2026
bc530d1
feat(_internal): Add PrivatePath for privacy-masking paths
tony Jan 3, 2026
8e6ed96
feat(cli[_colors]): Add formatting helpers for structured output
tony Jan 3, 2026
60fde8d
feat(cli[debug-info]): Add syntax highlighting and privacy masking
tony Jan 3, 2026
ca623f3
fix(cli[_colors]): Handle all tmux option formats in format_tmux_option
tony Jan 3, 2026
8fe234b
docs(cli[_colors]): Use narrative description in doctest
tony Jan 3, 2026
ef8c1db
feat(cli[load]): Add color support to _reattach and _load_detached
tony Jan 3, 2026
7e9892a
docs(cli[_colors]): Use narrative descriptions in module docstring
tony Jan 3, 2026
fa84073
refactor(cli): Move ANSI utilities from utils.py to _colors.py
tony Jan 3, 2026
0ffa101
fix(cli[debug-info]): Remove trailing newline when no stderr
tony Jan 3, 2026
5876411
feat(cli): Use PrivatePath for all path outputs
tony Jan 3, 2026
b8ac5bf
tests(cli): Add PrivatePath regression tests for path outputs
tony Jan 3, 2026
fd9596a
docs(cli[load]): Add doctests to load_plugins function
tony Jan 3, 2026
cf5477d
fix(cli[_colors]): Add RGB tuple validation in _interpret_color
tony Jan 3, 2026
8994b20
feat(cli): Add beautiful help output with usage examples
tony Jan 4, 2026
22d9281
docs(cli[_colors]): Complete module docstring with env var doctests
tony Jan 4, 2026
c3ed00c
docs(cli): Add doctests to private methods in _colors.py and _formatt…
tony Jan 4, 2026
ee0cba6
fix(cli): Propagate --color flag to prompt functions
tony Jan 4, 2026
aa6efdb
fix(cli): Pass color_mode to prompt functions in commands
tony Jan 4, 2026
202153f
refactor(cli): Remove unused HelpTheme import
tony Jan 4, 2026
b7965d8
style(cli): Use t.TypeAlias per CLAUDE.md convention
tony Jan 4, 2026
e56d1b4
fix(cli[_colors]): Handle fg/bg=0 in style() correctly
tony Jan 4, 2026
ab35441
fix(cli): Handle empty input in prompt() without default
tony Jan 4, 2026
89c96ee
fix(cli): Consistent PrivatePath usage in output messages
tony Jan 4, 2026
0d2818c
feat(cli): Add OutputFormatter for structured JSON/NDJSON output
tony Jan 4, 2026
dd5b7b9
feat(cli): Add --json, --ndjson, and --tree options to tmuxp ls
tony Jan 4, 2026
489a9af
fix(cli[freeze]): Use PrivatePath consistently for all path displays
tony Jan 4, 2026
4525919
feat(cli[ls]): Show local + global workspaces with source field
tony Jan 4, 2026
73e00ab
feat(cli[ls]): Add --full flag for complete config output
tony Jan 4, 2026
af4a683
feat(cli[search]): Add core search module with token parsing
tony Jan 4, 2026
b05957a
feat(cli[search]): Add workspace field extraction and match evaluation
tony Jan 4, 2026
3803918
feat(cli[search]): Add output formatting with match highlighting
tony Jan 4, 2026
0876037
feat(cli[search]): Add CLI integration and subparser
tony Jan 4, 2026
43b7d03
test(cli[search]): Add comprehensive tests for search command
tony Jan 4, 2026
d448100
docs(cli): Add ls and search examples to main help
tony Jan 4, 2026
53e924e
fix(tests): Add search to valid subcommands and fix TypedDict annotat…
tony Jan 4, 2026
cd4b9ba
feat(cli[search]): Show help when invoked without arguments
tony Jan 4, 2026
203dc44
test(cli[search]): Add test for no-args help behavior
tony Jan 4, 2026
0319a0f
feat(cli[debug-info]): Add --json output mode
tony Jan 4, 2026
3a0b9e4
docs(CHANGES): Add CLI Colors feature entry for #1006
tony Jan 4, 2026
a99a6a9
docs(CHANGES): Add search, ls enhancements, debug-info --json
tony Jan 4, 2026
6f943d2
docs(CHANGES): Mention jq compatibility for JSON output commands
tony Jan 4, 2026
3a2bf49
feat(cli[ls]): Add visual hierarchy with heading() and global workspa…
tony Jan 4, 2026
3455d69
ai(rules[AGENTS]): Add CLI Color Semantics guide (Revision 1)
tony Jan 4, 2026
b7ba3ed
refactor(cli[search]): Use heading() for section headers
tony Jan 4, 2026
805224d
refactor(cli): Use format_separator() for visual dividers
tony Jan 4, 2026
3bd5de5
fix(cli[utils]): Use PrivatePath for prompt default display
tony Jan 4, 2026
7b83299
feat(cli[search]): Include window and pane in default search fields
tony Jan 4, 2026
31d6409
feat(cli): Wire up help colorization with factory pattern
tony Jan 4, 2026
72434c0
fix(cli[_formatter]): Use removesuffix() instead of rstrip() for ANSI…
tony Jan 5, 2026
f30b63a
fix(cli[debug_info]): Guard empty strings in _private()
tony Jan 5, 2026
eac27b8
perf(tests[help_examples]): Replace subprocess with in-process argparse
tony Jan 10, 2026
338c750
fix(cli[utils]): Add feedback for invalid input in prompt_choices()
tony Jan 10, 2026
842ce68
fix(cli[ls]): Use tmp_path fixture instead of tempfile in doctest
tony Jan 10, 2026
c11ab5e
refactor(cli/_colors): Add dim parameter to _colorize() method
tony Jan 11, 2026
50c0360
ai(rules[AGENTS]): Add terminal color visibility guidelines
tony Jan 11, 2026
bc7fc9b
perf(conftest): Only load tmux fixtures for doctests that need them
tony Jan 10, 2026
bf25f42
docs(CHANGES): Add global workspace directories section to ls features
tony Jan 10, 2026
c1b318b
refactor(cli[ls]): Extract _render_global_workspace_dirs() helper
tony Jan 10, 2026
f6f01d0
fix(cli[_formatter]): Remove dead -d/--dir from OPTIONS_EXPECTING_VALUE
tony Jan 10, 2026
7a6bc2e
docs(cli[ls]): Add doctests to helper functions
tony Jan 10, 2026
5f1e269
refactor(tests[cli]): Consolidate color test fixtures
tony Jan 10, 2026
8e58e9c
refactor(tests[cli]): Remove duplicate TestGetOutputMode class
tony Jan 10, 2026
22d20a6
refactor(tests[cli]): Add ANSI color constants for readability
tony Jan 10, 2026
ce28431
tests(cli): add isolated_home fixture, refactor test_ls.py
tony Jan 10, 2026
026e698
fix(cli): resolve bold/dim ANSI code conflict in style()
tony Jan 10, 2026
42b054c
refactor(workspace): replace colorama with Colors class in finders.py
tony Jan 10, 2026
7230810
_internal/colors(refactor): Move colors module from cli/ to _internal/
tony Jan 10, 2026
f4e1e38
tests/_internal(refactor): Move color tests from cli/ to _internal/
tony Jan 10, 2026
bb435f2
log(refactor): Move tmuxp_echo to log module
tony Jan 10, 2026
fff98f5
cli/utils(fix[prompt_choices]): Fix type hint for choices parameter
tony Jan 10, 2026
52fa833
_internal/colors(fix[heading]): Use bright_cyan for heading() color
tony Jan 10, 2026
bc882d8
Add RGB value range validation to style()
tony Jan 10, 2026
385b4e9
ls.py: Fix help text jq example
tony Jan 10, 2026
11ec9ad
ai(rules[AGENTS]): Update heading() color to bright_cyan
tony Jan 10, 2026
101fe64
ai(rules[AGENTS]): Update colors module path to canonical location
tony Jan 10, 2026
e7abc7f
docs(api/internals): Add colors and private_path module documentation
tony Jan 10, 2026
cb8f05d
docs(api/cli): Add search command API documentation
tony Jan 10, 2026
a51c676
docs(cli): Add search command user documentation
tony Jan 10, 2026
a0490e6
docs(cli): Remove duplicate descriptions from command pages
tony Jan 11, 2026
e72bcd5
cli: Remove trailing colons from example category headings
tony Jan 11, 2026
76ad7a7
cli: Separate category labels from "examples:" in help output
tony Jan 11, 2026
d43aced
tests(cli): Convert class-based tests to functional tests
tony Jan 11, 2026
cecf056
fix(cli/load): Narrow exception handling in load_plugins()
tony Jan 11, 2026
1b97bfd
fix(cli/ls): Use specific exceptions for config parse errors
tony Jan 11, 2026
b1dd74f
fix(cli/search): Use specific exceptions for config parse errors
tony Jan 11, 2026
21b8f62
tests(cli/conftest): Clear TMUXP_CONFIGDIR in isolated_home fixture
tony Jan 11, 2026
1c62752
tests(_internal/colors): Add test for heading() with colors enabled
tony Jan 11, 2026
945a6cb
tests(cli/search): Add test for invalid regex pattern error
tony Jan 11, 2026
8b41806
fix(cli/search): Use consistent color semantics with ls command
tony Jan 11, 2026
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
66 changes: 66 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,69 @@ $ uv run pytest --cov
- **QA every edit**: Run formatting and tests before committing
- **Minimum Python**: 3.10+ (per pyproject.toml)
- **Minimum tmux**: 3.2+ (as per README)

## CLI Color Semantics (Revision 1, 2026-01-04)

The CLI uses semantic colors via the `Colors` class in `src/tmuxp/_internal/colors.py`. Colors are chosen based on **hierarchy level** and **semantic meaning**, not just data type.

### Design Principles

1. **Structural hierarchy**: Headers > Items > Details
2. **Semantic meaning**: What IS this element?
3. **Visual weight**: What should draw the eye first?
4. **Depth separation**: Parent elements should visually contain children

Inspired by patterns from **jq** (object keys vs values), **ripgrep** (path/line/match distinction), and **mise/just** (semantic method names).

### Hierarchy-Based Colors

| Level | Element Type | Method | Color | Examples |
|-------|--------------|--------|-------|----------|
| **L0** | Section headers | `heading()` | Bright cyan + bold | "Local workspaces:", "Global workspaces:" |
| **L1** | Primary content | `highlight()` | Magenta + bold | Workspace names (braintree, .tmuxp) |
| **L2** | Supplementary info | `info()` | Cyan | Paths (~/.tmuxp, ~/project/.tmuxp.yaml) |
| **L3** | Metadata/labels | `muted()` | Blue | Source labels (Legacy:, XDG default:) |

### Status-Based Colors (Override hierarchy when applicable)

| Status | Method | Color | Examples |
|--------|--------|-------|----------|
| Success/Active | `success()` | Green | "active", "18 workspaces" |
| Warning | `warning()` | Yellow | Deprecation notices |
| Error | `error()` | Red | Error messages |

### Example Output

```
Local workspaces: ← heading() bright_cyan+bold
.tmuxp ~/work/python/tmuxp/.tmuxp.yaml ← highlight() + info()

Global workspaces (~/.tmuxp): ← heading() + info()
braintree ← highlight()
cihai ← highlight()

Global workspace directories: ← heading()
Legacy: ~/.tmuxp (18 workspaces, active) ← muted() + info() + success()
XDG default: ~/.config/tmuxp (not found) ← muted() + info() + muted()
```

### Available Methods

```python
colors = Colors()
colors.heading("Section:") # Cyan + bold (section headers)
colors.highlight("item") # Magenta + bold (primary content)
colors.info("/path/to/file") # Cyan (paths, supplementary info)
colors.muted("label:") # Blue (metadata, labels)
colors.success("ok") # Green (success states)
colors.warning("caution") # Yellow (warnings)
colors.error("failed") # Red (errors)
```

### Key Rules

**Never use the same color for adjacent hierarchy levels.** If headers and items are both blue, they blend together. Each level must be visually distinct.

**Avoid dim/faint styling.** The ANSI dim attribute (`\x1b[2m`) is too dark to read on black terminal backgrounds. This includes both standard and bright color variants with dim.

**Bold may not render distinctly.** Some terminal/font combinations don't differentiate bold from normal weight. Don't rely on bold alone for visual distinction - pair it with color differences.
39 changes: 39 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,45 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force

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

### Features

#### CLI Colors (#1006)

New semantic color output for all CLI commands:

- New `--color` flag (auto/always/never) on root CLI for controlling color output
- Respects `NO_COLOR` and `FORCE_COLOR` environment variables per [no-color.org](https://no-color.org) standard
- Semantic color methods: `success()` (green), `warning()` (yellow), `error()` (red), `info()` (cyan), `highlight()` (magenta), `muted()` (blue)
- All commands updated with colored output: `load`, `ls`, `freeze`, `convert`, `import`, `edit`, `shell`, `debug-info`
- Interactive prompts enhanced with color support
- `PrivatePath` utility masks home directory as `~` for privacy protection in output
- Beautiful `--help` output with usage examples

#### Search Command (#1006)

New `tmuxp search` command for finding workspace files:

- Field-scoped search with prefixes: `name:`, `session:`, `path:`, `window:`, `pane:`
- Matching options: `-i` (ignore-case), `-S` (smart-case), `-F` (fixed-strings), `-w` (word)
- Logic operators: `--any` for OR, `-v` for invert match
- Output formats: human (with match highlighting), `--json`, `--ndjson` for automation and piping to `jq`
- Searches local (cwd and parents) and global (~/.tmuxp/) workspaces

#### Enhanced ls Command (#1006)

New output options for `tmuxp ls`:

- `--tree`: Display workspaces grouped by directory
- `--full`: Include complete parsed config content
- `--json` / `--ndjson`: Machine-readable output for automation and piping to `jq`
- Local workspace discovery from current directory and parents
- Source field distinguishes "local" vs "global" workspaces
- "Global workspace directories" section shows XDG vs legacy paths with status

#### JSON Output for debug-info (#1006)

- `tmuxp debug-info --json`: Structured JSON output for automation, issue reporting, and piping to `jq`

### Development

#### Makefile -> Justfile (#1005)
Expand Down
25 changes: 19 additions & 6 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,31 @@ def socket_name(request: pytest.FixtureRequest) -> str:
return f"tmuxp_test{next(namer)}"


# Modules that actually need tmux fixtures in their doctests
DOCTEST_NEEDS_TMUX = {
"tmuxp.workspace.builder",
}


@pytest.fixture(autouse=True)
def add_doctest_fixtures(
request: pytest.FixtureRequest,
doctest_namespace: dict[str, t.Any],
tmp_path: pathlib.Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Harness pytest fixtures to doctests namespace."""
if isinstance(request._pyfuncitem, DoctestItem) and shutil.which("tmux"):
doctest_namespace["server"] = request.getfixturevalue("server")
session: Session = request.getfixturevalue("session")
doctest_namespace["session"] = session
doctest_namespace["window"] = session.active_window
doctest_namespace["pane"] = session.active_pane
if isinstance(request._pyfuncitem, DoctestItem):
# Always provide lightweight fixtures
doctest_namespace["test_utils"] = test_utils
doctest_namespace["tmp_path"] = tmp_path
doctest_namespace["monkeypatch"] = monkeypatch

# Only load expensive tmux fixtures for modules that need them
module_name = request._pyfuncitem.dtest.globs.get("__name__", "")
if module_name in DOCTEST_NEEDS_TMUX and shutil.which("tmux"):
doctest_namespace["server"] = request.getfixturevalue("server")
session: Session = request.getfixturevalue("session")
doctest_namespace["session"] = session
doctest_namespace["window"] = session.active_window
doctest_namespace["pane"] = session.active_pane
1 change: 1 addition & 0 deletions docs/api/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ freeze
import_config
load
ls
search
shell
utils
```
Expand Down
8 changes: 8 additions & 0 deletions docs/api/cli/search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# tmuxp search - `tmuxp.cli.search`

```{eval-rst}
.. automodule:: tmuxp.cli.search
:members:
:show-inheritance:
:undoc-members:
```
14 changes: 14 additions & 0 deletions docs/api/internals/colors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Colors - `tmuxp._internal.colors`

:::{warning}
Be careful with these! Internal APIs are **not** covered by version policies. They can break or be removed between minor versions!

If you need an internal API stabilized please [file an issue](https://github.com/tmux-python/tmuxp/issues).
:::

```{eval-rst}
.. automodule:: tmuxp._internal.colors
:members:
:show-inheritance:
:undoc-members:
```
2 changes: 2 additions & 0 deletions docs/api/internals/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ If you need an internal API stabilized please [file an issue](https://github.com
:::

```{toctree}
colors
config_reader
private_path
types
```
14 changes: 14 additions & 0 deletions docs/api/internals/private_path.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Private path - `tmuxp._internal.private_path`

:::{warning}
Be careful with these! Internal APIs are **not** covered by version policies. They can break or be removed between minor versions!

If you need an internal API stabilized please [file an issue](https://github.com/tmux-python/tmuxp/issues).
:::

```{eval-rst}
.. automodule:: tmuxp._internal.private_path
:members:
:show-inheritance:
:undoc-members:
```
2 changes: 0 additions & 2 deletions docs/cli/convert.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

# tmuxp convert

Convert between YAML and JSON

```{eval-rst}
.. argparse::
:module: tmuxp.cli
Expand Down
3 changes: 0 additions & 3 deletions docs/cli/debug-info.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@

# tmuxp debug-info

Use to collect all relevant information for submitting an issue to
the project.

```{eval-rst}
.. argparse::
:module: tmuxp.cli
Expand Down
1 change: 1 addition & 0 deletions docs/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
load
shell
ls
search
```

```{toctree}
Expand Down
2 changes: 0 additions & 2 deletions docs/cli/ls.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

# tmuxp ls

List sessions.

```{eval-rst}
.. argparse::
:module: tmuxp.cli
Expand Down
13 changes: 13 additions & 0 deletions docs/cli/search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
(cli-search)=

(search-config)=

# tmuxp search

```{eval-rst}
.. argparse::
:module: tmuxp.cli
:func: create_parser
:prog: tmuxp
:path: search
```
Loading