Thanks for looking. Bug reports with a reproduction, and notes on where the documentation misled you, are the most useful thing you can bring right now.
How this project writes prose β README, CHANGES, commit messages,
docstrings, and source comments β is set out separately in
WRITING.md. Read that before changing any of it. The constraints
every change is held to, and the map of what is where, are in
AGENTS.md.
Check out the code from GitHub:
$ git clone git@github.com:tmux-python/tmuxp.git$ cd tmuxpThe easiest way to set up a dev environment is with uv, which manages the virtualenv and Python dependencies for you.
Create the virtualenv and install everything locked in uv.lock:
$ uv sync --all-extras --devTo refresh those packages later:
$ uv sync --all-extras --dev --upgradeThen prefix any Python command with uv run:
$ uv run [command]Prefer to manage the virtualenv yourself? Create one:
$ virtualenv .venvActivate it in your current shell:
$ source .venv/bin/activateInstall tmuxp in editable mode, so your edits take effect immediately:
$ pip install -e .With a uv-managed project, add the checkout as an editable dev dependency instead:
$ uv add --dev --editable .Prefer a one-off, pipx-style run while you hack? Call tmuxp through uvx:
$ uvx tmuxpCI is the order of record; every gate it runs has to pass before a change is done.
Format:
$ uv run ruff format .CI checks formatting without writing:
$ uv run ruff format --check .Lint:
$ uv run ruff check .Autofix what ruff can:
$ uv run ruff check . --fix --show-fixesType-check:
$ uv run mypyTest:
$ uv run py.testDocumentation is a gate, not a courtesy. [tool.pytest.ini_options] in
pyproject.toml sets testpaths = ["src/tmuxp", "tests", "docs"] and
addopts includes --doctest-modules, so every >>> example under
src/tmuxp/** and every doctest in a .py file under docs/ runs as part of
uv run py.test β there is no separate doctest step, and a green test run is
the proof. README.md is not in testpaths and is never executed; hold its
examples correct by review. Which blocks qualify, and the one mistake that
silently removes a test, are in
WRITING.md.
Before claiming a test or a gate works, show it failing. A gate that has never been red is an assumption.
- Namespace imports for the standard library:
import pathlib, thenpathlib.Path, notfrom pathlib import Path. Exception:from dataclasses import dataclass, field, since both are used as decorators/defaults, not namespaced. Third-party packages may usefrom X import Y. - Typing:
import typing as t, access via the namespace βt.Optional,t.NamedTuple. - Every file starts with
from __future__ import annotations; ruff'sisortconfig (required-importsinpyproject.toml) enforces it.
Ruff's select is deliberately unset in pyproject.toml β 0.16's curated
default rule set stays enabled, and extend-select layers this project's
additional linters (pydocstyle, flake8-bugbear, and the rest) on top rather
than replacing the defaults.
The suite lives in tests/, written with pytest. It runs against a real
tmux server on a separate socket (tmux -L test_case), so it never disturbs
your own sessions.
Write new tests as standalone functions, not class TestFoo: groupings β
descriptive function names and file organization carry the structure instead.
A couple of older suites still use classes; match the file you are in, prefer
functions in a new one.
- Prefer the
server,session,window,panefixtures fromtests/fixtures/over manual setup, and real tmux fixtures overMagicMock. - Use
retry_until(fromlibtmux.test.retry) for anything that waits on an async tmux operation instead of a bare sleep. - Use the
tmp_pathfixture instead of Python'stempfile, andmonkeypatchinstead ofunittest.mock. - Plugin tests import mock packages from
tests/fixtures/pluginsystem/plugins/β six fixture plugins (tmuxp_test_plugin_bwb,_bs,_r,_owc,_awf,_fail) exercising each plugin hook and a deliberate failure path. - Assert on
caplog.recordsattributes, not string matching oncaplog.text: scope capture withcaplog.at_level(logging.DEBUG, logger="libtmux.common"), filter records rather than index by position, and assert on schema (record.tmux_exit_code == 0, not"exit code 0" in caplog.text).caplog.record_tuplescannot accessextrafields.
$ just startRuns the suite once, then watches for changes via pytest-watcher.
Pass extra arguments through PYTEST_ADDOPTS:
$ env PYTEST_ADDOPTS="--verbose" just startPick a file:
$ env PYTEST_ADDOPTS="tests/workspace/test_builder.py" just startDrop into a single test and stop on the first error:
$ env PYTEST_ADDOPTS="-s -x -vv tests/workspace/test_builder.py::test_automatic_rename_option" \
just startDrop into pdb on the first error:
$ env PYTEST_ADDOPTS="-x -s --pdb" just startSet RETRY_TIMEOUT_SECONDS if a workspace-builder test is stubborn on your
machine:
$ env RETRY_TIMEOUT_SECONDS=10 uv run py.testA single file:
$ uv run py.test tests/workspace/test_builder.pyA single test inside it:
$ uv run py.test tests/test_config.py::test_export_jsonWatch the suite build sessions in real time by keeping a client open in a second terminal.
Terminal 1 β start a server on the test socket:
$ tmux -L test_caseTerminal 2 β from the checkout, run the builder tests:
$ uv run py.test tests/workspace/test_builder.pyTerminal 1 flickers as sessions build before your eyes β the building tmuxp normally hides from users.
Rebuild the docs whenever a source file changes:
$ just watch-docsOr build once:
$ just build-docsServe the built docs locally:
$ just serve-docsjust dev-docs runs the watcher and the server together; just design-docs
adds a static-file watch for theme work. docs/_build/ is generated β
never hand-edit it.
After you set up your environment, load the project's own workspace from the checkout root to see a real multi-pane dev layout:
$ tmuxp load .That loads .tmuxp.yaml at the project root.
Never create tags. Never push tags. The owner handles tagging and tag pushes, because a tag triggers the publish workflow. See Release commits.
The full release process β updating CHANGES, bumping the version, tagging,
and the CI publish to PyPI β is in
Releasing.
One subject per pull request. Unrelated cleanup found along the way belongs in its own commit, and usually in its own pull request.
Discuss a substantial change via an issue before making it.
Run the gates above before opening a pull request; update documentation if your change affects the public interface. A pull request merges once it has the sign-off of one other developer β if you cannot merge it yourself, request a reviewer to do so.
Commit format is in WRITING.md.
- Participants will be tolerant of opposing views.
- Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
- When interpreting the words and actions of others, participants should always assume good intentions.
- Behaviour which can be reasonably considered harassment will not be tolerated.
Based on Ruby's Community Conduct Guideline.
Please do not open a public issue for a vulnerability. Use GitHub's private
vulnerability reporting (the repository's Security tab β "Report a
vulnerability"), or contact the maintainer listed in pyproject.toml.