diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 9ed88302e..000000000 --- a/.coveragerc +++ /dev/null @@ -1,15 +0,0 @@ -[run] -omit = - test/* - */_vendor/* - */_* - pkg/* - */log.py - -[report] -exclude_lines = - pragma: no cover - def __repr__ - raise NotImplementedError - if __name__ == .__main__.: - def parse_args diff --git a/.cursor/rules/avoid-debug-loops.mdc b/.cursor/rules/avoid-debug-loops.mdc new file mode 100644 index 000000000..8a241ec99 --- /dev/null +++ b/.cursor/rules/avoid-debug-loops.mdc @@ -0,0 +1,57 @@ +--- +description: When stuck in debugging loops, break the cycle by minimizing to an MVP, removing debugging cruft, and documenting the issue completely for a fresh approach +globs: *.py +alwaysApply: true +--- +# Avoid Debug Loops + +When debugging becomes circular and unproductive, follow these steps: + +## Detection +- You have made multiple unsuccessful attempts to fix the same issue +- You are adding increasingly complex code to address errors +- Each fix creates new errors in a cascading pattern +- You are uncertain about the root cause after 2-3 iterations + +## Action Plan + +1. **Pause and acknowledge the loop** + - Explicitly state that you are in a potential debug loop + - Review what approaches have been tried and failed + +2. **Minimize to MVP** + - Remove all debugging cruft and experimental code + - Revert to the simplest version that demonstrates the issue + - Focus on isolating the core problem without added complexity + +3. **Comprehensive Documentation** + - Provide a clear summary of the issue + - Include minimal but complete code examples that reproduce the problem + - Document exact error messages and unexpected behaviors + - Explain your current understanding of potential causes + +4. **Format for Portability** + - Present the problem in quadruple backticks for easy copying: + +```` +# Problem Summary +[Concise explanation of the issue] + +## Minimal Reproduction Code +```python +# Minimal code example that reproduces the issue +``` + +## Error/Unexpected Output +``` +[Exact error messages or unexpected output] +``` + +## Failed Approaches +[Brief summary of approaches already tried] + +## Suspected Cause +[Your current hypothesis about what might be causing the issue] +```` + +This format enables the user to easily copy the entire problem statement into a fresh conversation for a clean-slate approach. diff --git a/.cursor/rules/dev-loop.mdc b/.cursor/rules/dev-loop.mdc new file mode 100644 index 000000000..d60a52109 --- /dev/null +++ b/.cursor/rules/dev-loop.mdc @@ -0,0 +1,187 @@ +--- +description: QA every edit +globs: *.py +alwaysApply: true +--- + +# Development Process + +## Project Stack + +The project uses the following tools and technologies: + +- **uv** - Python package management and virtual environments +- **ruff** - Fast Python linter and formatter +- **py.test** - Testing framework + - **pytest-watcher** - Continuous test runner +- **mypy** - Static type checking +- **doctest** - Testing code examples in documentation + +## 1. Start with Formatting + +Format your code first: + +``` +uv run ruff format . +``` + +## 2. Run Tests + +Verify that your changes pass the tests: + +``` +uv run py.test +``` + +For continuous testing during development, use pytest-watcher: + +``` +# Watch all tests +uv run ptw . + +# Watch and run tests immediately, including doctests +uv run ptw . --now --doctest-modules + +# Watch specific files or directories +uv run ptw . --now --doctest-modules src/libtmux/_internal/ +``` + +## 3. Commit Initial Changes + +Make an atomic commit for your changes using conventional commits. +Use `@git-commits.mdc` for assistance with commit message standards. + +## 4. Run Linting and Type Checking + +Check and fix linting issues: + +``` +uv run ruff check . --fix --show-fixes +``` + +Check typings: + +``` +uv run mypy +``` + +## 5. Verify Tests Again + +Ensure tests still pass after linting and type fixes: + +``` +uv run py.test +``` + +## 6. Final Commit + +Make a final commit with any linting/typing fixes. +Use `@git-commits.mdc` for assistance with commit message standards. + +## Development Loop Guidelines + +If there are any failures at any step due to your edits, fix them before proceeding to the next step. + +## Python Code Standards + +### Docstring Guidelines + +For `src/**/*.py` files, follow these docstring guidelines: + +1. **Use reStructuredText format** for all docstrings. + ```python + """Short description of the function or class. + + Detailed description using reStructuredText format. + + Parameters + ---------- + param1 : type + Description of param1 + param2 : type + Description of param2 + + Returns + ------- + type + Description of return value + """ + ``` + +2. **Keep the main description on the first line** after the opening `"""`. + +3. **Use NumPy docstyle** for parameter and return value documentation. + +### Doctest Guidelines + +For doctests in `src/**/*.py` files: + +1. **Use narrative descriptions** for test sections rather than inline comments: + ```python + """Example function. + + Examples + -------- + Create an instance: + + >>> obj = ExampleClass() + + Verify a property: + + >>> obj.property + 'expected value' + """ + ``` + +2. **Move complex examples** to dedicated test files at `tests/examples//test_.py` if they require elaborate setup or multiple steps. + +3. **Utilize pytest fixtures** via `doctest_namespace` for more complex test scenarios: + ```python + """Example with fixture. + + Examples + -------- + >>> # doctest_namespace contains all pytest fixtures from conftest.py + >>> example_fixture = getfixture('example_fixture') + >>> example_fixture.method() + 'expected result' + """ + ``` + +4. **Keep doctests simple and focused** on demonstrating usage rather than comprehensive testing. + +5. **Add blank lines between test sections** for improved readability. + +6. **Test your doctests continuously** using pytest-watcher during development: + ``` + # Watch specific modules for doctest changes + uv run ptw . --now --doctest-modules src/path/to/module.py + ``` + +### Pytest Testing Guidelines + +1. **Use existing fixtures over mocks**: + - Use fixtures from conftest.py instead of `monkeypatch` and `MagicMock` when available + - For instance, if using libtmux, use provided fixtures: `server`, `session`, `window`, and `pane` + - Document in test docstrings why standard fixtures weren't used for exceptional cases + +2. **Preferred pytest patterns**: + - Use `tmp_path` (pathlib.Path) fixture over Python's `tempfile` + - Use `monkeypatch` fixture over `unittest.mock` + +### Import Guidelines + +1. **Prefer namespace imports**: + - Import modules and access attributes through the namespace instead of importing specific symbols + - Example: Use `import enum` and access `enum.Enum` instead of `from enum import Enum` + - This applies to standard library modules like `pathlib`, `os`, and similar cases + +2. **Standard aliases**: + - For `typing` module, use `import typing as t` + - Access typing elements via the namespace: `t.NamedTuple`, `t.TypedDict`, etc. + - Note primitive types like unions can be done via `|` pipes and primitive types like list and dict can be done via `list` and `dict` directly. + +3. **Benefits of namespace imports**: + - Improves code readability by making the source of symbols clear + - Reduces potential naming conflicts + - Makes import statements more maintainable diff --git a/.cursor/rules/git-commits.mdc b/.cursor/rules/git-commits.mdc new file mode 100644 index 000000000..f9c0980db --- /dev/null +++ b/.cursor/rules/git-commits.mdc @@ -0,0 +1,95 @@ +--- +description: git-commits: Git commit message standards and AI assistance +globs: git-commits: Git commit message standards and AI assistance | *.git/* .gitignore .github/* CHANGELOG.md CHANGES.md +alwaysApply: true +--- +# Optimized Git Commit Standards + +## Commit Message Format +``` +Component/File(commit-type[Subcomponent/method]): Concise description + +why: Explanation of necessity or impact. +what: +- Specific technical changes made +- Focused on a single topic + +refs: #issue-number, breaking changes, or relevant links +``` + +## Component Patterns +### General Code Changes +``` +Component/File(feat[method]): Add feature +Component/File(fix[method]): Fix bug +Component/File(refactor[method]): Code restructure +``` + +### Packages and Dependencies +| Language | Standard Packages | Dev Packages | Extras / Sub-packages | +|------------|------------------------------------|-------------------------------|-----------------------------------------------| +| General | `lang(deps):` | `lang(deps[dev]):` | | +| Python | `py(deps):` | `py(deps[dev]):` | `py(deps[extra]):` | +| JavaScript | `js(deps):` | `js(deps[dev]):` | `js(deps[subpackage]):`, `js(deps[dev{subpackage}]):` | + +#### Examples +- `py(deps[dev]): Update pytest to v8.1` +- `js(deps[ui-components]): Upgrade Button component package` +- `js(deps[dev{linting}]): Add ESLint plugin` + +### Documentation Changes +Prefix with `docs:` +``` +docs(Component/File[Subcomponent/method]): Update API usage guide +``` + +### Test Changes +Prefix with `tests:` +``` +tests(Component/File[Subcomponent/method]): Add edge case tests +``` + +## Commit Types Summary +- **feat**: New features or enhancements +- **fix**: Bug fixes +- **refactor**: Code restructuring without functional change +- **docs**: Documentation updates +- **chore**: Maintenance (dependencies, tooling, config) +- **test**: Test-related updates +- **style**: Code style and formatting + +## General Guidelines +- Subject line: Maximum 50 characters +- Body lines: Maximum 72 characters +- Use imperative mood (e.g., "Add", "Fix", not "Added", "Fixed") +- Limit to one topic per commit +- Separate subject from body with a blank line +- Mark breaking changes clearly: `BREAKING:` +- Use `See also:` to provide external references + +## AI Assistance Workflow in Cursor +- Stage changes with `git add` +- Use `@commit` to generate initial commit message +- Review and refine generated message +- Ensure adherence to these standards + +## Good Commit Example +``` +Pane(feat[capture_pane]): Add screenshot capture support + +why: Provide visual debugging capability +what: +- Implement capturePane method with image export +- Integrate with existing Pane component logic +- Document usage in Pane README + +refs: #485 +See also: https://example.com/docs/pane-capture +``` + +## Bad Commit Example +``` +fixed stuff and improved some functions +``` + +These guidelines ensure clear, consistent commit histories, facilitating easier code review and maintenance. \ No newline at end of file diff --git a/.cursor/rules/notes-llms-txt.mdc b/.cursor/rules/notes-llms-txt.mdc new file mode 100644 index 000000000..ac1709773 --- /dev/null +++ b/.cursor/rules/notes-llms-txt.mdc @@ -0,0 +1,42 @@ +--- +description: LLM-friendly markdown format for notes directories +globs: notes/**/*.md,**/notes/**/*.md +alwaysApply: true +--- + +# Instructions for Generating LLM-Optimized Markdown Content + +When creating or editing markdown files within the specified directories, adhere to the following guidelines to ensure the content is optimized for LLM understanding and efficient token usage: + +1. **Conciseness and Clarity**: + - **Be Brief**: Present information succinctly, avoiding unnecessary elaboration. + - **Use Clear Language**: Employ straightforward language to convey ideas effectively. + +2. **Structured Formatting**: + - **Headings**: Utilize markdown headings (`#`, `##`, `###`, etc.) to organize content hierarchically. + - **Lists**: Use bullet points (`-`) or numbered lists (`1.`, `2.`, etc.) to enumerate items clearly. + - **Code Blocks**: Enclose code snippets within triple backticks (```) to distinguish them from regular text. + +3. **Semantic Elements**: + - **Emphasis**: Use asterisks (`*`) or underscores (`_`) for italicizing text to denote emphasis. + - **Strong Emphasis**: Use double asterisks (`**`) or double underscores (`__`) for bold text to highlight critical points. + - **Inline Code**: Use single backticks (`) for inline code references. + +4. **Linking and References**: + - **Hyperlinks**: Format links using `[Link Text](mdc:URL)` to provide direct access to external resources. + - **References**: When citing sources, use footnotes or inline citations to maintain readability. + +5. **Avoid Redundancy**: + - **Eliminate Repetition**: Ensure that information is not unnecessarily repeated within the document. + - **Use Summaries**: Provide brief summaries where detailed explanations are not essential. + +6. **Standard Compliance**: + - **llms.txt Conformance**: Structure the document in alignment with the `llms.txt` standard, which includes: + - An H1 heading with the project or site name. + - A blockquote summarizing the project's purpose. + - Additional markdown sections providing detailed information. + - H2-delimited sections containing lists of URLs for further details. + +By following these guidelines, the markdown files will be tailored for optimal LLM processing, ensuring that the content is both accessible and efficiently tokenized for AI applications. + +For more information on the `llms.txt` standard, refer to the official documentation: https://llmstxt.org/ diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..d202a332d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 02c7e1688..000000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,70 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - schedule: - - cron: '16 5 * * 2' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://git.io/codeql-language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3e1abf99d..0f771d99f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -10,12 +10,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10"] + python-version: ['3.13'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Filter changed file paths to outputs - uses: dorny/paths-filter@v2.7.0 + uses: dorny/paths-filter@v3.0.2 id: changes with: filters: | @@ -26,39 +26,38 @@ jobs: - 'docs/**' - 'examples/**' python_files: - - 'libtmux/**' - - poetry.lock + - 'src/libtmux/**' + - uv.lock - pyproject.toml - name: Should publish if: steps.changes.outputs.docs == 'true' || steps.changes.outputs.root_docs == 'true' || steps.changes.outputs.python_files == 'true' run: echo "PUBLISH=$(echo true)" >> $GITHUB_ENV - - name: Install poetry + - name: Install uv if: env.PUBLISH == 'true' - run: pipx install "poetry==1.1.15" + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true - name: Set up Python ${{ matrix.python-version }} if: env.PUBLISH == 'true' - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - cache: 'poetry' + run: uv python install ${{ matrix.python-version }} - name: Install dependencies [w/ docs] if: env.PUBLISH == 'true' - run: poetry install --extras "docs lint" + run: uv sync --all-extras --dev - name: Print python versions if: env.PUBLISH == 'true' run: | python -V - poetry run python -V + uv run python -V - name: Build documentation if: env.PUBLISH == 'true' run: | - pushd docs; make SPHINXBUILD='poetry run sphinx-build' html; popd + pushd docs; make SPHINXBUILD='uv run sphinx-build' html; popd - name: Push documentation to S3 uses: jakejarvis/s3-sync-action@v0.5.1 @@ -69,8 +68,8 @@ jobs: AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: "us-west-1" # optional: defaults to us-east-1 - SOURCE_DIR: "docs/_build/html" # optional: defaults to entire repository + AWS_REGION: 'us-west-1' # optional: defaults to us-east-1 + SOURCE_DIR: 'docs/_build/html' # optional: defaults to entire repository - name: Purge cache on Cloudflare if: env.PUBLISH == 'true' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e5443194d..affbc0b0e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,23 +10,34 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.10"] - tmux-version: ["2.6", "2.7", "2.8", "3.0a", "3.1b", "3.2a", "3.3a", "master"] + python-version: ['3.9', '3.13'] + tmux-version: ['2.6', '2.7', '2.8', '3.0a', '3.1b', '3.2a', '3.3a', '3.4', '3.5', 'master'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Install poetry - run: pipx install "poetry==1.1.15" + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - cache: 'poetry' + run: uv python install ${{ matrix.python-version }} + + - name: Test runtime dependencies + run: | + uv run --no-dev -p python${{ matrix.python-version }} -- python -c ' + from libtmux import common, constants, exc, formats, neo, pane, server, session, window, __version__ + server = server.Server() + print("libtmux version:", __version__) + print("libtmux Server:", server) + ' + + - name: Install dependencies + run: uv sync --all-extras --dev - name: Setup tmux build cache for tmux ${{ matrix.tmux-version }} id: tmux-build-cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/tmux-builds/tmux-${{ matrix.tmux-version }} key: tmux-${{ matrix.tmux-version }} @@ -46,36 +57,62 @@ jobs: cd ~ tmux -V - - name: Install python dependencies - run: | - poetry install -E "test coverage lint" + - name: Lint with ruff check + run: uv run ruff check . - - name: Lint with flake8 - run: | - poetry run flake8 + - name: Format with ruff format + run: uv run ruff format . --check - name: Lint with mypy - run: poetry run mypy . + run: uv run mypy . - name: Print python versions run: | python -V - poetry run python -V + uv run python -V - name: Test with pytest continue-on-error: ${{ matrix.tmux-version == 'master' }} run: | + sudo apt install libevent-2.1-7 export PATH=$HOME/tmux-builds/tmux-${{ matrix.tmux-version }}/bin:$PATH ls $HOME/tmux-builds/tmux-${{ matrix.tmux-version }}/bin tmux -V - poetry run py.test --cov=./ --cov-report=xml - - uses: codecov/codecov-action@v2 + uv run py.test --cov=./ --cov-append --cov-report=xml -n auto --verbose + env: + COV_CORE_SOURCE: . + COV_CORE_CONFIG: .coveragerc + COV_CORE_DATAFILE: .coverage.eager + - uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} + release: + runs-on: ubuntu-latest + needs: build + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + + strategy: + matrix: + python-version: ['3.13'] + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + + - name: Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} + + - name: Install dependencies + run: uv sync --all-extras --dev + - name: Build package if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - run: poetry build + run: uv build - name: Publish package if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') diff --git a/.gitignore b/.gitignore index d8c8a65a4..4f9f3d29e 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,9 @@ target/ # Monkeytype monkeytype.sqlite3 + +# Claude code +**/CLAUDE.md +**/CLAUDE.local.md +**/CLAUDE.*.md +**/.claude/settings.local.json diff --git a/.python-version b/.python-version index ad0e6c1a4..4eba2a62e 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.10.6 3.9.13 3.8.13 3.7.12 +3.13.0 diff --git a/.tmuxp-before-script.sh b/.tmuxp-before-script.sh deleted file mode 100755 index 0721faabd..000000000 --- a/.tmuxp-before-script.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -poetry shell --no-ansi --no-interaction &2> /dev/null -poetry install --no-ansi --no-interaction &2> /dev/null diff --git a/.tmuxp.yaml b/.tmuxp.yaml index c1a39a8a1..38ca2cad9 100644 --- a/.tmuxp.yaml +++ b/.tmuxp.yaml @@ -1,14 +1,13 @@ session_name: libtmux start_directory: ./ # load session relative to config location (project root). -before_script: ./.tmuxp-before-script.sh shell_command_before: -- '[ -f .venv/bin/activate ] && source .venv/bin/activate && reset' +- uv virtualenv --quiet > /dev/null 2>&1 && clear windows: - window_name: libtmux focus: True layout: main-horizontal options: - main-pane-height: 35 + main-pane-height: 67% panes: - focus: true - pane @@ -17,7 +16,7 @@ windows: - window_name: docs layout: main-horizontal options: - main-pane-height: 35 + main-pane-height: 67% start_directory: docs/ panes: - focus: true diff --git a/.tool-versions b/.tool-versions index f9d413269..c17a23a75 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -poetry 1.1.15 -python 3.10.6 3.9.13 3.8.13 3.7.12 +uv 0.7.13 +python 3.13.5 3.12.11 3.11.13 3.10.18 3.9.23 diff --git a/.vim/coc-settings.json b/.vim/coc-settings.json new file mode 100644 index 000000000..d542a7262 --- /dev/null +++ b/.vim/coc-settings.json @@ -0,0 +1,19 @@ +{ + "[markdown][python]": { + "coc.preferences.formatOnSave": true + }, + "python.analysis.autoSearchPaths": true, + "python.analysis.typeCheckingMode": "basic", + "python.analysis.useLibraryCodeForTypes": true, + "python.formatting.provider": "ruff", + "python.linting.ruffEnabled": true, + "python.linting.mypyEnabled": true, + "python.linting.flake8Enabled": false, + "python.linting.pyflakesEnabled": false, + "python.linting.pycodestyleEnabled": false, + "python.linting.banditEnabled": false, + "python.linting.pylamaEnabled": false, + "python.linting.pylintEnabled": false, + "pyright.organizeimports.provider": "ruff", + "pyright.testing.provider": "pytest", +} diff --git a/.windsurfrules b/.windsurfrules new file mode 100644 index 000000000..e5c31a13e --- /dev/null +++ b/.windsurfrules @@ -0,0 +1,136 @@ +# libtmux Python Project Rules + + +- uv - Python package management and virtual environments +- ruff - Fast Python linter and formatter +- py.test - Testing framework + - pytest-watcher - Continuous test runner +- mypy - Static type checking +- doctest - Testing code examples in documentation + + + +- Use a consistent coding style throughout the project +- Format code with ruff before committing +- Run linting and type checking before finalizing changes +- Verify tests pass after each significant change + + + +- Use reStructuredText format for all docstrings in src/**/*.py files +- Keep the main description on the first line after the opening `"""` +- Use NumPy docstyle for parameter and return value documentation +- Format docstrings as follows: + ```python + """Short description of the function or class. + + Detailed description using reStructuredText format. + + Parameters + ---------- + param1 : type + Description of param1 + param2 : type + Description of param2 + + Returns + ------- + type + Description of return value + """ + ``` + + + +- Use narrative descriptions for test sections rather than inline comments +- Format doctests as follows: + ```python + """ + Examples + -------- + Create an instance: + + >>> obj = ExampleClass() + + Verify a property: + + >>> obj.property + 'expected value' + """ + ``` +- Add blank lines between test sections for improved readability +- Keep doctests simple and focused on demonstrating usage +- Move complex examples to dedicated test files at tests/examples//test_.py +- Utilize pytest fixtures via doctest_namespace for complex scenarios + + + +- Run tests with `uv run py.test` before committing changes +- Use pytest-watcher for continuous testing: `uv run ptw . --now --doctest-modules` +- Fix any test failures before proceeding with additional changes + + + +- Make atomic commits with conventional commit messages +- Start with an initial commit of functional changes +- Follow with separate commits for formatting, linting, and type checking fixes + + + +- Use the following commit message format: + ``` + Component/File(commit-type[Subcomponent/method]): Concise description + + why: Explanation of necessity or impact. + what: + - Specific technical changes made + - Focused on a single topic + + refs: #issue-number, breaking changes, or relevant links + ``` + +- Common commit types: + - **feat**: New features or enhancements + - **fix**: Bug fixes + - **refactor**: Code restructuring without functional change + - **docs**: Documentation updates + - **chore**: Maintenance (dependencies, tooling, config) + - **test**: Test-related updates + - **style**: Code style and formatting + +- Prefix Python package changes with: + - `py(deps):` for standard packages + - `py(deps[dev]):` for development packages + - `py(deps[extra]):` for extras/sub-packages + +- General guidelines: + - Subject line: Maximum 50 characters + - Body lines: Maximum 72 characters + - Use imperative mood (e.g., "Add", "Fix", not "Added", "Fixed") + - Limit to one topic per commit + - Separate subject from body with a blank line + - Mark breaking changes clearly: `BREAKING:` + + + +- Use fixtures from conftest.py instead of monkeypatch and MagicMock when available +- For instance, if using libtmux, use provided fixtures: server, session, window, and pane +- Document in test docstrings why standard fixtures weren't used for exceptional cases +- Use tmp_path (pathlib.Path) fixture over Python's tempfile +- Use monkeypatch fixture over unittest.mock + + + +- Prefer namespace imports over importing specific symbols +- Import modules and access attributes through the namespace: + - Use `import enum` and access `enum.Enum` instead of `from enum import Enum` + - This applies to standard library modules like pathlib, os, and similar cases +- For typing, use `import typing as t` and access via the namespace: + - Access typing elements as `t.NamedTuple`, `t.TypedDict`, etc. + - Note primitive types like unions can be done via `|` pipes + - Primitive types like list and dict can be done via `list` and `dict` directly +- Benefits of namespace imports: + - Improves code readability by making the source of symbols clear + - Reduces potential naming conflicts + - Makes import statements more maintainable + diff --git a/CHANGES b/CHANGES index b8f51b2c2..467523245 100644 --- a/CHANGES +++ b/CHANGES @@ -1,26 +1,1188 @@ # Changelog -To install the unreleased libtmux version, see [developmental releases](https://libtmux.git-pull.com/quickstart.html#developmental-releases). +For instructions on installing the development version of libtmux, refer to +[development releases](https://libtmux.git-pull.com/quickstart.html#developmental-releases). -[pip](https://pip.pypa.io/en/stable/): +To install via [pip](https://pip.pypa.io/en/stable/), use: ```console $ pip install --user --upgrade --pre libtmux ``` -## libtmux 0.15.x (unreleased) +## libtmux 0.47.x (Yet to be released) -- _Insert changes/features/fixes for next release here_ + + +- _Future release notes will be placed here_ + +## libtmux 0.46.2 (2025-05-26) + +### Development + +- Add `StrPath` type support for `start_directory` parameters (#596, #597, #598): + - `Server.new_session`: Accept PathLike objects for session start directory + - `Session.new_window`: Accept PathLike objects for window start directory + - `Pane.split` and `Pane.split_window`: Accept PathLike objects for pane start directory + - `Window.split` and `Window.split_window`: Accept PathLike objects for pane start directory + - Enables `pathlib.Path` objects alongside strings for all start directory parameters + - Includes comprehensive tests for all parameter types (None, empty string, string paths, PathLike objects) + + Thank you @Data5tream for the initial commit in #596! + +## libtmux 0.46.1 (2025-03-16) + +_Maintenance only, no bug fixes or new features_ + +A version branch has been created at v0.46.x, the next release of v0.47.0 may +be a few months in waiting (watchers / snapshots are in development in #587). + +### Documentation + +- Typo fix for `Pane.send_keys` (#593), thank you @subbyte! + +## libtmux 0.46.0 (2025-02-25) + +### Breaking + +#### Imports removed from libtmux.test (#580) + +Root-level of imports from `libtmux.test` are no longer possible. + +```python +# Before 0.46.0 +from libtmux.test import namer +``` + +```python +# From 0.46.0 onward +from libtmux.test.named import namer +``` + +Same thing with constants: + +```python +# Before 0.46.0 +from libtmux.test import ( + RETRY_INTERVAL_SECONDS, + RETRY_TIMEOUT_SECONDS, + TEST_SESSION_PREFIX +) +``` + +```python +# From 0.46.0 onward +from libtmux.test.constants import ( + RETRY_INTERVAL_SECONDS, + RETRY_TIMEOUT_SECONDS, + TEST_SESSION_PREFIX +) +``` + +### Development + +#### Test helpers: Increased coverage (#580) + +Several improvements to the test helper modules: + +- Enhanced `EnvironmentVarGuard` in `libtmux.test.environment` to better handle variable cleanup +- Added comprehensive test suites for test constants and environment utilities +- Improved docstrings and examples in `libtmux.test.random` with test coverage annotations +- Fixed potential issues with environment variable handling during tests +- Added proper coverage markers to exclude type checking blocks from coverage reports + +## libtmux 0.45.0 (2025-02-23) + +### Breaking Changes + +#### Test helpers: Refactor + +Test helper functionality has been split into focused modules (#578): + +- `libtmux.test` module split into: + - `libtmux.test.constants`: Test-related constants (`TEST_SESSION_PREFIX`, etc.) + - `libtmux.test.environment`: Environment variable mocking + - `libtmux.test.random`: Random string generation utilities + - `libtmux.test.temporary`: Temporary session/window management + +**Breaking**: Import paths have changed. Update imports: + +```python +# Old (0.44.x and earlier) +from libtmux.test import ( + TEST_SESSION_PREFIX, + get_test_session_name, + get_test_window_name, + namer, + temp_session, + temp_window, + EnvironmentVarGuard, +) +``` + +```python +# New (0.45.0+) +from libtmux.test.constants import TEST_SESSION_PREFIX +from libtmux.test.environment import EnvironmentVarGuard +from libtmux.test.random import get_test_session_name, get_test_window_name, namer +from libtmux.test.temporary import temp_session, temp_window +``` + +### Development + +- CI: Check for runtime dependencies (#574) + + Kudos @ppentchev for inspiration on the command + ([comment](https://github.com/tmux-python/libtmux/pull/572#issuecomment-2663642923)). + +## libtmux 0.44.2 (2025-02-17) + +### Bug fix + +- Fix `typing_extensions` issue by wrapping it in `TYPE_CHECKING`, continuation of #564, via #572. + +### Development + +- Improved test organization and coverage in `test_common.py` (#570): + - Consolidated version-related tests into parametrized fixtures using NamedTuples + - Added comprehensive test cases for various version formats (master, next, OpenBSD, dev, rc) + - Improved test readability with clear test IDs and logical grouping + - Consistent use of pytest parametrize convention across test suite +- Fix broken test for `test_window_rename` (#570) + +## libtmux 0.44.1 (2025-02-17) + +### Packaging + +- Types: Only import `typing_extensions` when necessary, via #563, @ppentchev! + +## libtmux 0.44.0 (2025-02-16) + +### New Features + +#### Context Managers support (#566) + +Added context manager support for all major object types: + +- `Server`: Automatically kills the server when exiting the context +- `Session`: Automatically kills the session when exiting the context +- `Window`: Automatically kills the window when exiting the context +- `Pane`: Automatically kills the pane when exiting the context + +Example usage: + +```python +with Server() as server: + with server.new_session() as session: + with session.new_window() as window: + with window.split() as pane: + pane.send_keys('echo "Hello"') + # Do work with the pane + # Everything is cleaned up automatically when exiting contexts +``` + +This makes it easier to write clean, safe code that properly cleans up tmux resources. + +## libtmux 0.43.0 (2025-02-15) + +### New Features + +#### Server Initialization Callbacks + +Server now accepts 2 new optional params, `socket_name_factory` and `on_init` callbacks (#565): + +- `socket_name_factory`: Callable that generates unique socket names for new servers +- `on_init`: Callback that runs after server initialization +- Useful for creating multiple servers with unique names and tracking server instances +- Socket name factory is tried after socket_name, maintaining backward compatibility + +#### New test fixture: `TestServer` + +Add `TestServer` pytest fixture for creating temporary tmux servers (#565): + +- Creates servers with unique socket names that clean up after themselves +- Useful for testing interactions between multiple tmux servers +- Includes comprehensive test coverage and documentation +- Available in doctest namespace + +### Documentation + +- Fix links to the "Topics" section +- More docs for "Traversal" Topic (#567) + +## libtmux 0.42.1 (2024-02-15) + +### Bug fixes + +- tests: Import `Self` in a `TYPE_CHECKING` guard to prevent dependency issues. + Via #562, Thank you @ppentchev! + +### Development + +- dev dependencies: Include `typing-extensions` for Python version < 3.11 via + the `testing` and `lint` groups, via #564. + +## libtmux 0.42.0 (2025-02-02) + +### Bug fixes + +- `tmux_cmd`: Migrate to to `text=True` + + This deprecates usage of `console_to_str()` and `str_from_console()`. + + Resolves #558 via #560. + +- compat: Remove `console_to_str()` and `str_from_console()` + + These are both deprecated artifacts of libtmux' Python 2.x compatiblity layer. + +## libtmux 0.41.0 (2025-02-02) + +### Fixes + +- {meth}`Server.__repr__()`: Use {meth}`os.geteuid()` for default `socket_path`. Thank you @lazysegtree! + (#557, resolves #556) + +### Documentation + +- `Server`: Fix `colors` docstring to note it accepts `88` or `256`, Thank you + @TravisDart! (via #544) + +### Development + +#### chore: Implement PEP 563 deferred annotation resolution (#555) + +- Add `from __future__ import annotations` to defer annotation resolution and reduce unnecessary runtime computations during type checking. +- Enable Ruff checks for PEP-compliant annotations: + - [non-pep585-annotation (UP006)](https://docs.astral.sh/ruff/rules/non-pep585-annotation/) + - [non-pep604-annotation (UP007)](https://docs.astral.sh/ruff/rules/non-pep604-annotation/) + +For more details on PEP 563, see: https://peps.python.org/pep-0563/ + +## libtmux 0.40.1 (2024-12-24) + +### Bug fix + +- `Server.new_session`: Fix handling of environmental variables passed to new + sessions. Thank you @ppentchev! (#553) + +## libtmux 0.40.0 (2024-12-20) + +_Maintenance only, no bug fixes or new features_ + +### Breaking + +- `_global` renamed to `global_` + +### Development + +- Aggressive automated lint fixes via `ruff` (#550) + + via ruff v0.8.4, all automated lint fixes, including unsafe and previews were applied for Python 3.9: + + ```sh + ruff check --select ALL . --fix --unsafe-fixes --preview --show-fixes; ruff format . + ``` + +- Tests: Stability fixes for legacy `test_select_pane` test (#552) + +## libtmux 0.39.0 (2024-11-26) + +_Maintenance only, no bug fixes or new features_ + +### Breaking changes + +- Drop Python 3.8. end of life was October 7th, 2024 (#548) + + tmuxp 1.48.0 was the last release for Python 3.8. + + The minimum python for tmuxp as of 1.49.0 is Python 3.9 + +## libtmux 0.38.1 (2024-11-26) + +- Keep minimum Python version at 3.8 for now. + +## libtmux 0.38.0 (2024-11-26) + +### Breaking changes + +#### Project and package management: poetry to uv (#547) + +[uv] is the new package and project manager for the project, replacing Poetry. + +[uv]: https://github.com/astral-sh/uv + +#### Build system: poetry to hatchling (#547) + +[Build system] moved from [poetry] to [hatchling]. + +[Build system]: https://packaging.python.org/en/latest/tutorials/packaging-projects/#choosing-a-build-backend +[poetry]: https://github.com/python-poetry/poetry +[hatchling]: https://hatch.pypa.io/latest/ + +### Development + +- Code quality: Use f-strings in more places (#540) + + via [ruff 0.4.2](https://github.com/astral-sh/ruff/blob/v0.4.2/CHANGELOG.md). + +[uv]: https://github.com/astral-sh/uv + +### Documentation + +- Fix docstrings in `query_list` for `MultipleObjectsReturned` and + `ObjectDoesNotExist`. + +## libtmux 0.37.0 (04-21-2024) + +_Maintenance only, no bug fixes or new features_ + +### Testing + +- Add `pytest-xdist` ([PyPI](https://pypi.org/project/pytest-xdist/), [GitHub](https://github.com/pytest-dev/pytest-xdist)) for parallel testing (#522). + + pytest: + + ```console + py.test -n auto + ``` + + pytest-watcher: + + ```console + env PYTEST_ADDOPTS='-n auto' make start + ``` + + entr(1): + + ```console + make watch_test test="-n auto" + ``` + +- Improve flakey tests: + + - `retry_until()` tests: Relax clock in `assert` (#522). + - `tests/test_pane.py::test_capture_pane_start`: Use `retry_until()` to poll, + improve correctness of test (#522). + +### Documentation + +- Automatically linkify links that were previously only text. + +### Development + +- poetry: 1.8.1 -> 1.8.2 + + See also: https://github.com/python-poetry/poetry/blob/1.8.2/CHANGELOG.md + +## libtmux 0.36.0 (2024-03-24) + +_Maintenance only, no bug fixes or new features_ + +### Development + +- Aggressive automated lint fixes via `ruff` (#539) + + via ruff v0.3.4, all automated lint fixes, including unsafe and previews were applied: + + ```sh + ruff check --select ALL . --fix --unsafe-fixes --preview --show-fixes; ruff format . + ``` + + Branches were treated with: + + ```sh + git rebase \ + --strategy-option=theirs \ + --exec 'poetry run ruff check --select ALL . --fix --unsafe-fixes --preview --show-fixes; poetry run ruff format .; git add src tests; git commit --amend --no-edit' \ + origin/master + ``` + +## libtmux 0.35.1 (2024-03-23) + +### Bug fix + +- {attr}`Server.attached_sessions` fix for when multiple clients attached, thank you @patrislav1 (#537) + + - #538 fix to `QueryList`. + +## libtmux 0.35.0 (2024-03-17) + +### Breaking changes + +- Eliminate redundant targets / `window_index`'s across codebase (#536). + +## libtmux 0.34.0 (2024-03-17) + +### Breaking changes + +#### Command target change (#535) + +Commands: All `cmd()` methods using custom or overridden targets must use the keyword argument +`target`. This avoids entanglement with inner shell values that include `-t` for +other purposes. These methods include: + +- {meth}`Server.cmd()` +- {meth}`Session.cmd()` +- {meth}`Window.cmd()` +- {meth}`Pane.cmd()` + +## libtmux 0.33.0 (2024-03-17) + +### Breaking changes + +#### Improved new sessions (#532) + +- `Session.new_window()`: + + - Learned `direction`, via {class}`~libtmux.constants.WindowDirection`). + - [PEP 3102] keyword-only arguments after window name (#534). + +- Added {meth}`Window.new_window()` shorthand to create window based on that + window's position. + +[PEP 3102]: https://www.python.org/dev/peps/pep-3102/ + +#### Improved window splitting (#532) + +- `Window.split_window()` to {meth}`Window.split()` + + - Deprecate `Window.split_window()` + +- `Pane.split_window()` to {meth}`Pane.split()` + + - Deprecate `Pane.split_window()` + - Learned `direction`, via {class}`~libtmux.constants.PaneDirection`). + + - Deprecate `vertical` and `horizontal` in favor of `direction`. + + - Learned `zoom` + +#### Tweak: Pane position (#532) + +It's now possible to retrieve the position of a pane in a window via a +`bool` helper:: + +- {attr}`Pane.at_left` +- {attr}`Pane.at_right` +- {attr}`Pane.at_bottom` +- {attr}`Pane.at_right` + +### Development + +- poetry: 1.7.1 -> 1.8.1 + + See also: https://github.com/python-poetry/poetry/blob/1.8.1/CHANGELOG.md + +## libtmux 0.32.0 (2024-03-01) + +_Maintenance only, no bug fixes or new features_ + +### Packaging + +- Add implicit imports to `__init__.py` (#531), thank you @ssbarnea. + +### Development + +- ruff 0.2.2 -> 0.3.0 + +## libtmux 0.31.0 (2024-02-17) + +### Cleanups (#527) + +- Streamline `{Server,Session,Window,Pane}.cmd()`, across all usages to: + - Use `cmd: str` as first positional + - Removed unused keyword arguments `**kwargs` + +### Renamings (#527) + +- `Session.attached_window` renamed to {meth}`Session.active_window` + + - `Session.attached_window` deprecated + +- `Session.attached_pane` renamed to {meth}`Session.active_pane` + + - `Session.attached_pane` deprecated + +- `Window.attached_pane` renamed to {meth}`Window.active_pane` + + - `Window.attached_pane` deprecated + +### Improvements (#527) + +- `Server.attached_windows` now uses `QueryList`'s `.filter()` + +### Documentation (#527) + +- Document `.cmd` in README and quickstart +- Add doctests and improve docstrings to `cmd()` methods across: + - {meth}`Server.cmd()` + - {meth}`Session.cmd()` + - {meth}`Window.cmd()` + - {meth}`Pane.cmd()` + +### Post-release: v0.31.0post0 (2024-02-17) + +- Documentation updates + +## libtmux 0.30.2 (2024-02-16) + +### Development + +- Updated `TMUX_MAX_VERSION` from 3.3 to 3.4 + +## libtmux 0.30.1 (2024-02-16) + +### Fixes + +- Adjusted pytest plugin and test module: Updated to use renamed methods from + version 0.30.0. + +## libtmux 0.30.0 (2024-02-16) + +### Additions + +- Introduced {meth}`Pane.kill()` method + +### Modifications + +- `Window.select_window()` renamed to {meth}`Window.select()` + - Deprecated `Window.select_window()` +- `Pane.select_pane()` renamed to {meth}`Pane.select()` + - Deprecated `Pane.pane_select()` +- `Session.attach_session()` renamed to {meth}`Session.attach()` + - Deprecated `Session.attach_session()` +- `Server.kill_server()` renamed to {meth}`Server.kill()` + - Deprecated `Server.kill_server()` +- `Session.kill_session()` renamed to {meth}`Session.kill()` + - Deprecated `Session.kill_session()` +- `Window.kill_window()` renamed to {meth}`Window.kill()` + - Deprecated `Window.kill_window()` + +### Enhancements + +- {meth}`Server.new_session()`: Support environment variables +- {meth}`Window.split_window()`: Support `size` via `-l` + + Supports columns/rows (`size=10`) and percentage (`size='10%'`) + +## libtmux 0.29.0 (2024-02-16) + +#### Fixes + +- Use {exc}`DeprecationWarning` for APIs set to be deprecated (#526) + +#### Testing + +- pytest: Ignore {exc}`DeprecationWarning` by default (#526) + +## libtmux 0.28.1 (2024-02-15) + +_Maintenance only, no bug fixes or new features_ + +#### Testing + +- CI: Bump actions to node 20+ versions + +#### Documentation + +- Refine docs and add migration for v0.28.0 + +## libtmux 0.28.0 (2024-02-14) + +### Breaking changes + +#### Detached / unselected by default (#523) + +To ensure consistency and principle of least surprise, keep these set to +not use `-a` unless explicitly specified. + +Breaking: {meth}`Session.new_window()` + {meth}`Window.split_window()` no longer attaches by default. + +- 0.28.0 and greater: Defaults to `attach=False`. +- 0.27.1 and below: Defaults to `attach=True`. + +To keep the old behavior in 0.28.0 and beyond, pass `attach=True` explicitly. + +### Improved resizing (#523) + +- Breaking: `Pane.resize_pane()` renamed to {meth}`Pane.resize()` (#523) + + This convention will be more consistent with {meth}`Window.resize()`. + +- Breaking: {meth}`Pane.resize()`'s params changed (#523) + + - No longer accepts `-U`, `-D`, `-L`, `-R` directly, instead accepts + {class}`~libtmux.constants.ResizeAdjustmentDirection`). + +- {meth}`Pane.resize()`: + + - Accept adjustments via `adjustment_direction` w/ + {class}`~libtmux.constants.ResizeAdjustmentDirection` + `adjustment`. + + - Learned to accept manual `height` and / or `width` (columns/rows or percentage) + + - Zoom (and unzoom) + +- {meth}`Window.resize()`: Newly added + +Tip: If {meth}`Pane.resize()` was not taking affect <= 0.27.1, try to resize with +{meth}`Window.resize()` first. + +### Fixes + +- {meth}`Window.refresh()` and {meth}`Pane.refresh()`: Refresh more underlying state (#523) +- {meth}`Obj._refresh`: Allow passing args (#523) + + e.g. `-a` (all) to `list-panes` and `list-windows` + +- `Server.panes`: Fix listing of panes (#523) + + Would list only panes in attached session, rather than all in a server. + +### Improvement + +- Pane, Window: Improve parsing of option values that return numbers + (#520) +- `Obj._refresh`: Allow passing `list_extra_args` to ensure `list-windows` and + `list-panes` can return more than the target (#523) + +### Tests + +- pytest: Fix `usefixture` warning (#519) +- ci: Add tmux 3.4 to test matrix (#909) + +## libtmux 0.27.1 (2024-02-07) + +### Packaging + +- Include `MIGRATION` in source distribution tarball (#517, for #508) + +## libtmux 0.27.0 (2024-02-07) + +### Improvement + +- QueryList typings (#515) + + - This improves the annotations in descendant objects such as: + + - `Server.sessions` + - `Session.windows` + - `Window.panes` + + - Bolster tests (ported from `libvcs`): doctests and pytests + +## libtmux 0.26.0 (2024-02-06) + +### Breaking changes + +- `get_by_id()` (already deprecated) keyword argument renamed from `id` to + `Server.get_by_id(session_id)`, `Session.get_by_id(window_id)`, and `Window.get_by_id(pane_id)` (#514) + +### Documentation + +- Various docstring fixes and tweaks (#514) + +### Development + +- Strengthen linting (#514) + + - Add flake8-commas (COM) + + - https://docs.astral.sh/ruff/rules/#flake8-commas-com + - https://pypi.org/project/flake8-commas/ + + - Add flake8-builtins (A) + + - https://docs.astral.sh/ruff/rules/#flake8-builtins-a + - https://pypi.org/project/flake8-builtins/ + + - Add flake8-errmsg (EM) + + - https://docs.astral.sh/ruff/rules/#flake8-errmsg-em + - https://pypi.org/project/flake8-errmsg/ + +### CI + +- Move CodeQL from advanced configuration file to GitHub's default + +## libtmux 0.25.0 (2023-11-25) + +### Improvement + +- `Server.__eq__`, `Session.__eq__`, `Window.__eq__`, `Pane.__eq__` now returns `False` instead of raising `AssertionError` when type mismatches (#505, #510) + + Thank you @m1guelperez for `Window.__eq__`! (#505) + +### Development + +- ci: Add pydocstyle rule to ruff (#509) + +### Documentation + +- Add docstrings to functions, methods, classes, and packages (#509) + +## libtmux 0.24.1 (2023-11-23) + +### Packaging + +- Remove `requirements/` folder, which was unused and deprecated by + pyproject.toml (#507) +- pyproject: Add `gp-libs` to `test` dependency group + +## libtmux 0.24.0 (2023-11-19) + +_Maintenance only, no bug fixes or new features_ + +### Breaking changes + +- Python 3.7 Dropped (#497) + +### Packaging + +- Move pytest configuration to `pyproject.toml` (#499) +- Poetry: 1.5.1 -> 1.6.1 (#497), 1.6.1 -> 1.7.0 (direct to trunk) + + See also: https://github.com/python-poetry/poetry/blob/1.7.0/CHANGELOG.md + +- Packaging (poetry): Fix development dependencies + + Per [Poetry's docs on managing dependencies] and `poetry check`, we had it wrong: Instead of using extras, we should create these: + + ```toml + [tool.poetry.group.group-name.dependencies] + dev-dependency = "1.0.0" + ``` + + Which we now do. + + [Poetry's docs on managing dependencies]: https://python-poetry.org/docs/master/managing-dependencies/ + +### Development + +- Move formatting from `black` to [`ruff format`] (#506) + + This retains the same formatting style of `black` while eliminating a + dev dependency by using our existing rust-based `ruff` linter. + + [`ruff format`]: https://docs.astral.sh/ruff/formatter/ + +- CI: Update action packages to fix warnings + + - [dorny/paths-filter]: 2.7.0 -> 2.11.1 + - [codecov/codecov-action]: 2 -> 3 + + [dorny/paths-filter]: https://github.com/dorny/paths-filter + [codecov/codecov-action]: https://github.com/codecov/codecov-action + +## libtmux 0.23.2 (2023-09-09) + +_Maintenance only, no bug fixes or new features_ + +### Breaking changes + +- Cut last python 3.7 release (EOL was June 27th, 2023) + + For security updates, a 0.23.x branch can be maintained for a limited time, + if necessary. + +## libtmux 0.23.1 (2023-09-02) + +_Maintenance only, no bug fixes or new features_ + +### Development + +- Automated typo fixes from [typos-cli]: + + ```console + typos --format brief --write-changes + ``` + + [typos-cli]: https://github.com/crate-ci/typos + +- ruff: Remove ERA / `eradicate` plugin + + This rule had too many false positives to trust. Other ruff rules have been beneficial. + +## libtmux 0.23.0 (2023-08-20) + +_Maintenance only, no bug fixes or new features_ + +### Development + +- Code quality improved via [ruff] rules (#488) + + This includes fixes made by hand, and with ruff's automated fixes. Despite + selecting additional rules, which include import sorting, ruff runs nearly + instantaneously when checking the whole codebase. + +### Post-release: v0.23.0post0 (2023-08-20) + +- Fixes code comments cleaned up by `ruff`, but missed in QA. In the future, + even when using an automated tool, we will review more thoroughly. + +### Post-release: v0.23.0post1 (2023-08-26) + +- Fixes for more `ERA001` issues. + +### Post-release: v0.23.0post2 (2023-08-28) + +- Yet more `ERA001` fixes. + +## libtmux 0.22.2 (2023-08-20) + +### Development + +- build system: Remove `setuptools` requirement (#495, in related to #493, #494) + +## libtmux 0.22.1 (2023-05-28) + +_Maintenance only, no bug fixes or new features_ + +### Development + +- Add back `black` for formatting + + This is still necessary to accompany `ruff`, until it replaces black. + +## libtmux 0.22.0 (2023-05-27) + +_Maintenance only, no bug fixes or new features_ + +### Internal improvements + +- Move formatting, import sorting, and linting to [ruff]. + + This rust-based checker has dramatically improved performance. Linting and + formatting can be done almost instantly. + + This change replaces black, isort, flake8 and flake8 plugins. + +- poetry: 1.4.0 -> 1.5.0 + + See also: https://github.com/python-poetry/poetry/releases/tag/1.5.0 + +[ruff]: https://ruff.rs + +## libtmux 0.21.1 (2023-04-07) + +### Development + +- Update mypy to 1.2.0 + +### Fixes + +- SkipDefaultFieldsReprMixin: Fix typing for mypy 1.2.0 + +## libtmux 0.21.0 (2023-01-29) + +### Breaking internal change + +- Default format separator (`LIBTMUX_TMUX_FORMAT_SEPARATOR`): `|` -> `␞` (#475, + in re: #471, #472) + + Fixes `buffer_sample` with pipes causing `fetch_objs()`-powered listings to fail unexpectedly. + +## libtmux 0.20.0 (2023-01-15) + +### What's new + +- Server.new_session: Accept `x` and `y`, thanks + @rockandska (#469) +- New test fixture: `session_params`. The dict is used directly in the `session` + pytest fixture (#470) + +## libtmux 0.19.1 (2022-01-07) + +### Fixes + +- `Window.set_window_option()`: Remove `.refresh()` (#467) + + See also: https://github.com/tmux-python/tmuxp/issues/860 + +## libtmux 0.19.0 (2022-01-07) + +### New features + +- `pane.capture_pane()` learned to accept `start` and `end` line numbers (#465) + +## libtmux 0.18.3 (2023-01-07) + +### Improvement + +- `fetch_objs` now raises `ObjectDoesNotExist` with detailed information on + lookup that failed (#466) + +## libtmux 0.18.2 (2022-12-30) + +### Fixes + +- Server: Launching of new session with default socket (#857) + +## libtmux 0.18.1 (2022-12-28) + +### Fixes + +- Window.panes: Fix docstring +- Remove unused code documentation + +## libtmux 0.18.0 (2022-12-27) + +### Breaking + +- Server: Add `__repr__` and set `socket_path` if none set. + + Before (0.17 and below): + + ```python + + ``` + + New `__repr__` (0.18+): + + ```python + Server(socket_name=test) + ``` + + ```python + Server(socket_path=/tmp/tmux-1000/default) + ``` + +## libtmux 0.17.2 (2022-12-27) + +- Server: Move `_list_panes` and `_update_panes` to deprecated + +## libtmux 0.17.1 (2022-12-27) + +### Fixes + +- Documentation fixes +- Add deprecation warning to `Server.children`, `Session.children`, + `Window.children`. + +## libtmux 0.17.0 (2022-12-26) + +### Breaking changes (#426) + +- Finding objects / relations + + - 0.16 and below: `session._windows()`, `session.list_windows()`, etc. + + 0.17 and after: {attr}`session.windows ` + + - 0.16 and below: `session.find_where({'window_name': my_window})` + + 0.17 and after: {meth}`session.windows.get(window_name=my_window, default=None) ` + + - If not found and not `default`, raises {exc}`~libtmux._internal.query_list.ObjectDoesNotExist` + - If multiple objects found, raises {exc}`~libtmux._internal.query_list.MultipleObjectsReturned` + + - 0.16 and below: `session.where({'window_name': my_window})` + + 0.17 and after: {meth}`session.windows.filter(window_name=my_window) ` + +- Accessing attributes + + - 0.16 and below: `window['id']` + + 0.17 and after: `window.id` + + - 0.16 and below: `window.get('id')` + + 0.17 and after: `window.id` + + - 0.16 and below: `window.get('id', None)` + + 0.17 and after: `getattr(window, 'id', None)` + +### New features + +#### Detect if server active (#448) + +- `Server.is_alive()` +- `Server.raise_if_dead()` + +### Internal + +- Remove unused `sphinx-click` development dependency + +## libtmux 0.16.1 (2022-12-12) + +### Fixes + +- Remove reliance on `packaging.version.Version` (#461) + + This is too critical of a package to pin a dependency as it may interfere with other packages the user relies on. In addition, libtmux doesn't need strict compatibility with `packaging`. + +## libtmux 0.16.0 (2022-12-10) + +### Breaking changes + +- Fix `distutils` warning, vendorize `LegacyVersion` (#351) + + Removal of reliancy on `distutils.version.LooseVersion`, which does not + support `tmux(1)` versions like `3.1a`. + + Fixes warning: + + > DeprecationWarning: distutils Version classes are deprecated. Use + > packaging.version instead. + + The temporary workaround, before 0.16.0 (assuming _setup.cfg_): + + ```ini + [tool:pytest] + filterwarnings = + ignore:.* Use packaging.version.*:DeprecationWarning:: + ignore:The frontend.Option(Parser)? class.*:DeprecationWarning:: + ``` + +### Features + +- `Window.split_window()` and `Session.new_window()` now support an optional + dictionary of environmental variables, via (#453), credit @zappolowski. + +## libtmux 0.15.10 (2022-11-05) + +_There will be more improvements over the coming weeks and months to shore up +flakiness across shells and environments._ + +### Tests + +- Compatibility improvement for `test_capture_pane` and `env` (#452), credit: + @zappolowski! +- Remove more BASHisms from tests (#455) + +## libtmux 0.15.9 (2022-10-30) + +### Bug fix + +- `tmux_cmd()`: Fix raise of TmuxCommandNotFound (#450) + +### CI + +- Use python 3.11 (#451) + +### Packaging + +- Add python 3.11 to trove classifiers (#451) + +### Development + +- Add python 3.11 to asdf and pyenv configurations (#451) + +## libtmux 0.15.8 (2022-10-02) + +### Bug fix + +- `Session.new_window()`: Improve support for `window_name: ''` downstream in tmuxp (#444, credit: @trankchung) + +## libtmux 0.15.7 (2022-09-23) + +- Move `.coveragerc` -> `pyproject.toml` (#443) + +## libtmux 0.15.6 (2022-09-23) + +_Maintenance only, no bug fixes or new features_ + +### Packaging + +- Remove `MANIFEST.in` + + This is handled by poetry's `include` in pyproject.toml. + +## libtmux 0.15.5 (2022-09-23) + +_Maintenance only, no bug fixes or new features_ + +### Packaging + +- Remove `.tmuxp-before-script.sh` from `.tmuxp.yaml` + +## libtmux 0.15.4 (2022-09-21) + +### Fixes + +- Use stable `pytest` API imports where possible to fix issues in downstream + packaging on Arch (#441, via #442) + +### Packaging + +- Add `.tmuxp-before-script.sh` (used by `.tmuxp.yaml`) and `conftest.py` to + source distributoins (#441, via #442) + +## libtmux 0.15.3 (2022-09-20) + +### Tests / docs + +- Examples for pytest plugin (#439) +- Move conftest.py to root level (#440) + + - https://docs.pytest.org/en/stable/deprecations.html#pytest-plugins-in-non-top-level-conftest-files + - Less conftest.py files + - We can now run py.test for `README.md` without needing to proxy through + `docs/index.md` + +## libtmux 0.15.2 (2022-09-17) + +**Maintenance release, no features or fixes** + +### Tests + +- pytest plugin: Initial tests (for testing the plugin itself, #423) + +### Packaging + +- pyproject.toml: Note pytest framework in trove classifiers + +### Infrastructure + +- CI speedups (#428) + + - Avoid fetching unused apt package + - Split out release to separate job so the PyPI Upload docker image isn't pulled on normal + runs + +## libtmux 0.15.1 (2022-09-11) + +### Packaging + +- pyproject.toml: Drop old issues package, remove anther package from grouping + +### Documentation + +- Cleanup quickstart page + +## libtmux 0.15.0 (2022-09-10) + +### New features + +- Added a [pytest plugin](https://libtmux.git-pull.com/pytest-plugin.html), #411. ### Breaking changes +- Remove `common.which()` in favor of {func}`shutil.which`, Credit: + @rocksandska, via #407 - Fixes #402: {func}`common.tmux_cmd` will only strip _trailing_ empty lines. Before this change, all empty lines were filtered out. This will lead to a more accurate behavior when using {meth}`Pane.capture_pane`. Credit: @rockandska, via #405. +- Source files for `libtmux` modules moved to `src/`, via #414. + +### Development + +- Add [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) (#408) +- Add [flake8-comprehensions](https://github.com/adamchainz/flake8-comprehensions) (#409) + +### Tests + +- Test doctests in documentation via + [pytest-doctest-docutils](https://gp-libs.git-pull.com/doctest/pytest.html) (#410) ### Documentation -- Move to sphinx-autoissues, #406 +- Examples updated for correctness, #412 (cherry-picked from #410) +- Render changelog in [linkify_issues](https://gp-libs.git-pull.com/linkify_issues/) (#410) +- Fix Table of contents rendering with sphinx autodoc with + [sphinx_toctree_autodoc_fix](https://gp-libs.git-pull.com/sphinx_toctree_autodoc_fix/) (#410) ## libtmux 0.14.2 (2022-08-17) @@ -179,14 +1341,14 @@ $ pip install --user --upgrade --pre libtmux ~~Final python 3.7 and 3.8 release~~ - ~~Bug fixes and security updates will go to + ~~Fixes and security updates will go to [`v0.11.x`](https://github.com/tmux-python/libtmux/tree/v0.11.x)~~ - Internal: Use new separator to split `tmux(1)` formatting information (#289, #343) The separator is configurable via `LIBTMUX_TMUX_FORMAT_SEPARATOR`. If you ever - have compatiblity issues in the future let us know which default works best + have compatibility issues in the future let us know which default works best across versions. Credit: @JonathanRaiman and @jagguli @@ -241,8 +1403,8 @@ across any difficulty please see #346. ## libtmux 0.10.0 (2021-06-16) - #321: Convert to markdown -- #271: Fix {}`select_window()` by providing the session ID as - argument to {}`-t`. Thanks @Flowdalic +- #271: Fix `select_window()` by providing the session ID as + argument to `-t`. Thanks @Flowdalic - Drop python 3.5 support ## libtmux 0.9.0 (2021-06-14) @@ -346,7 +1508,7 @@ Python 2.7 support dropped. ## libtmux 0.7.4 (2017-08-19) -- #65 Add session id to commands, thanks [@askedrelic][@askedrelic] +- #65 Add session id to commands, thanks [@askedrelic] ## libtmux 0.7.3 (2017-05-29) @@ -367,9 +1529,9 @@ Python 2.7 support dropped. - Add support for tmux 2.4, pypy and pypy3 - Overhaul error handling when setting and showing options - - Added {}`handle_option_error` for handling option errors + - Added `handle_option_error` for handling option errors - Added {exc}`libtmux.exc.OptionError` base exception - - Added {exc}`libtmux.exc.InvalidOption` and {}`libtmux.exc.AmbiguousOption` + - Added {exc}`libtmux.exc.InvalidOption` and `libtmux.exc.AmbiguousOption` - {exc}`libtmux.exc.UnknownOption` now extends {exc}`libtmux.exc.OptionError` - Overhaul version checking @@ -401,7 +1563,7 @@ Python 2.7 support dropped. ## libtmux 0.6.2 (2017-01-19) -- #197 use {}`LooseVersion` instead of {}`StrictVersion` for version +- #197 use `LooseVersion` instead of `StrictVersion` for version checks. Thanks @minijackson. - Pin packages with pyup.io - #21 Readme fix from @huwenchao. @@ -417,7 +1579,7 @@ Python 2.7 support dropped. - Raise exception for invalid session names. tmux does not allow names that are empty, contain periods or colons. -- Remove unused `target_sesssion` param in +- Remove unused `target_session` param in `Server.attach_session` and `Server.switch_client`. ## libtmux 0.5.1 (2016-08-18) @@ -456,7 +1618,7 @@ Python 2.7 support dropped. ## libtmux 0.1.0 (2016-05-22) -- libtmux forked from [tmuxp][tmuxp]. +- libtmux forked from [tmuxp]. [tmuxp]: https://github.com/tmux-python/tmuxp [@askedrelic]: https://github.com/askedrelic diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 000000000..5cf6612a4 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,13 @@ +cff-version: 1.2.0 +message: >- + If you use this software, please cite it as below. + NOTE: Change "x.y" by the version you use. If you are unsure about which version + you are using run: `pip show libtmux`." +authors: +- family-names: "Narlock" + given-names: "Tony" + orcid: "https://orcid.org/0000-0002-2568-415X" +title: "libtmux" +type: software +version: x.y +url: "https://libtmux.git-pull.com" diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index a40f8cae6..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include README.md LICENSE CHANGES pyproject.toml .tmuxp.yaml -include requirements/*.txt -recursive-include docs *.md diff --git a/MIGRATION b/MIGRATION new file mode 100644 index 000000000..6d62cf917 --- /dev/null +++ b/MIGRATION @@ -0,0 +1,213 @@ +# Migration notes + +Migration and deprecation notes for libtmux are here, see {ref}`changelog` as +well. + +```{admonition} Welcome on board! 👋 +1. 📌 For safety, **always** pin the package +2. 📖 Check the migration notes _(You are here)_ +3. đŸ“Ŗ If you feel something got deprecated and it interrupted you - past, present, or future - voice your opinion on the [tracker]. + + We want to make libtmux fun, reliable, and useful for users. + + API changes can be painful. + + If we can do something to draw the sting, we'll do it. We're taking a balanced approach. That's why these notes are here! + + (Please pin the package. 🙏) + + [tracker]: https://github.com/tmux-python/libtmux/discussions +``` + +## Upcoming Release + +_Detailed migration steps for the next version will be posted here._ + + + +## libtmux 0.46.0 (2025-02-25) + +#### Imports removed from libtmux.test (#580) + +Root-level of imports from `libtmux.test` are no longer possible. + +```python +# Before 0.46.0 +from libtmux.test import namer +``` + +```python +# From 0.46.0 onward +from libtmux.test.named import namer +``` + +Same thing with constants: + +```python +# Before 0.46.0 +from libtmux.test import ( + RETRY_INTERVAL_SECONDS, + RETRY_TIMEOUT_SECONDS, + TEST_SESSION_PREFIX +) +``` + +```python +# From 0.46.0 onward +from libtmux.test.constants import ( + RETRY_INTERVAL_SECONDS, + RETRY_TIMEOUT_SECONDS, + TEST_SESSION_PREFIX +) +``` + +## libtmux 0.45.0 (2025-02-23) + +### Test helpers: Module moves + +Test helper functionality has been split into focused modules (#578): + +- `libtmux.test` module split into: + - `libtmux.test.constants`: Test-related constants (`TEST_SESSION_PREFIX`, etc.) + - `libtmux.test.environment`: Environment variable mocking + - `libtmux.test.random`: Random string generation utilities + - `libtmux.test.temporary`: Temporary session/window management + +**Breaking**: Import paths have changed. Update imports: + +```python +# Old (0.44.x and earlier) +from libtmux.test import ( + TEST_SESSION_PREFIX, + get_test_session_name, + get_test_window_name, + namer, + temp_session, + temp_window, + EnvironmentVarGuard, +) +``` + +```python +# New (0.45.0+) +from libtmux.test.constants import TEST_SESSION_PREFIX +from libtmux.test.environment import EnvironmentVarGuard +from libtmux.test.random import get_test_session_name, get_test_window_name, namer +from libtmux.test.temporary import temp_session, temp_window +``` + +## 0.35.0: Commands require explicit targets (2024-03-17) + +### Commands require explicit targets (#535) + +- {meth}`Server.cmd()`, {meth}`Session.cmd()`, {meth}`Window.cmd()`, {meth}`Pane.cmd()` require passing `target` instead of `['-t', target]`, `['-tTargetName']`, etc. This change is to avoid issues mistakenly interpreting `-t` in other shell values as targets. + + Before: + + ```python + session.cmd('send-keys', 'echo hello', '-t', '0') + ``` + + With 0.35.0 and after: + + ```python + session.cmd('send-keys', 'echo hello', target='0') + ``` + +## 0.33.0: Deprecations for splitting (2024-03-03) + +### Deprecations (#532) + +- `Window.split_window()` to {meth}`Window.split()` +- `Pane.split_window()` to {meth}`Pane.split()` + +## 0.31.0: Renaming and command cleanup (2024-02-17) + +### Cleanups (#527) + +- Commands: Param change + + {meth}`Server.cmd()`, {meth}`Session.cmd()`, {meth}`Window.cmd()`, {meth}`Pane.cmd()` + + - Use `cmd: str` as first positional + - Removed unused keyword arguments `**kwargs` + +### Renamings (#527) + +- `Session.attached_window` renamed to {meth}`Session.active_window` + - `Session.attached_window` deprecated +- `Session.attached_pane` renamed to {meth}`Session.active_pane` + - `Session.attached_pane` deprecated +- `Window.attached_pane` renamed to {meth}`Window.active_pane` + - `Window.attached_pane` deprecated + +## 0.28.0: Resizing and detached by default (2024-02-15) + +#### Detach by default + +- {meth}`Session.new_window()` + {meth}`Window.split_window()` no longer attaches by default (#523) + + - 0.28.0 and greater: Defaults to `attach=False`. + - 0.27.1 and below: Defaults to `attach=True`. + + For the old behavior in 0.28.0 and beyond, pass `attach=True` explicitly. + +#### Resizing panes + +- `Pane.resize_pane()` renamed to {meth}`Pane.resize()` (via #523) + + This convention will be more consistent with {meth}`Window.resize()`. + +- {meth}`Pane.resize_pane()`'s params changed (#523) + + - No longer accepts `-U`, `-D`, `-L`, `-R` directly, instead accepts + {class}`~libtmux.constants.ResizeAdjustmentDirection` (see below). + + - 0.27.1 and below: `pane.resize_pane("-D", 20)`, `pane.resize_pane("-R", 20)` + + - 0.28.0 and beyond: + + ```python + from libtmux.constants import ResizeAdjustmentDirection + pane.resize_pane(adjustment_direction=ResizeAdjustmentDirection.Down, adjustment=25) + pane.resize_pane( + adjustment_direction=ResizeAdjustmentDirection.Right, adjustment=25 + ) + ``` + +## 0.17.0: Simplified attributes (2022-12-26) + +### Finding objects / relations + +- 0.16 and below: `session._windows()`, `session.list_windows()`, etc. + + 0.17 and after: {attr}`session.windows ` + +- 0.16 and below: `session.find_where({'window_name': my_window})` + + 0.17 and after: {meth}`session.windows.get(window_name=my_window, default=None) ` + + - If not found and not `default`, raises {exc}`~libtmux._internal.query_list.ObjectDoesNotExist` + - If multiple objects found, raises {exc}`~libtmux._internal.query_list.MultipleObjectsReturned` + +- 0.16 and below: `session.where({'window_name': my_window})` + + 0.17 and after: {meth}`session.windows.filter(window_name=my_window) ` + +### Accessing attributes + +- 0.16 and below: `window['id']` + + 0.17 and after: `window.id` + +- 0.16 and below: `window.get('id')` + + 0.17 and after: `window.id` + +- 0.16 and below: `window.get('id', None)` + + 0.17 and after: `getattr(window, 'id', None)` + + diff --git a/Makefile b/Makefile index c9609c7bc..d786f7e7b 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ PY_FILES= find . -type f -not -path '*/\.*' | grep -i '.*[.]py$$' 2> /dev/null +DOC_FILES= find . -type f -not -path '*/\.*' | grep -i '.*[.]rst\$\|.*[.]md\$\|.*[.]css\$\|.*[.]py\$\|mkdocs\.yml\|CHANGES\|TODO\|.*conf\.py' 2> /dev/null +ALL_FILES= find . -type f -not -path '*/\.*' | grep -i '.*[.]py\$\|.*[.]rst\$\|.*[.]md\$\|.*[.]css\$\|.*[.]py\$\|mkdocs\.yml\|CHANGES\|TODO\|.*conf\.py' 2> /dev/null entr_warn: @@ -6,23 +8,17 @@ entr_warn: @echo " ! File watching functionality non-operational ! " @echo " " @echo "Install entr(1) to automatically run tasks on file change." - @echo "See http://entrproject.org/ " + @echo "See https://eradman.com/entrproject/ " @echo "----------------------------------------------------------" -isort: - poetry run isort `${PY_FILES}` - -black: - poetry run black `${PY_FILES}` - test: - poetry run py.test $(test) + uv run py.test $(test) start: - $(MAKE) test; poetry run ptw . + $(MAKE) test; uv run ptw . watch_test: - if command -v entr > /dev/null; then ${PY_FILES} | entr -c $(MAKE) test; else $(MAKE) test entr_warn; fi + if command -v entr > /dev/null; then ${ALL_FILES} | entr -c $(MAKE) test; else $(MAKE) test entr_warn; fi build_docs: $(MAKE) -C docs html @@ -42,14 +38,17 @@ serve_docs: dev_docs: $(MAKE) -j watch_docs serve_docs -flake8: - poetry run flake8 +ruff_format: + uv run ruff format . + +ruff: + uv run ruff check . -watch_flake8: - if command -v entr > /dev/null; then ${PY_FILES} | entr -c $(MAKE) flake8; else $(MAKE) flake8 entr_warn; fi +watch_ruff: + if command -v entr > /dev/null; then ${PY_FILES} | entr -c $(MAKE) ruff; else $(MAKE) ruff entr_warn; fi mypy: - poetry run mypy `${PY_FILES}` + uv run mypy `${PY_FILES}` watch_mypy: if command -v entr > /dev/null; then ${PY_FILES} | entr -c $(MAKE) mypy; else $(MAKE) mypy entr_warn; fi @@ -58,7 +57,7 @@ format_markdown: prettier --parser=markdown -w *.md docs/*.md docs/**/*.md CHANGES monkeytype_create: - poetry run monkeytype run `poetry run which py.test` + uv run monkeytype run `uv run which py.test` monkeytype_apply: - poetry run monkeytype list-modules | xargs -n1 -I{} sh -c 'poetry run monkeytype apply {}' + uv run monkeytype list-modules | xargs -n1 -I{} sh -c 'uv run monkeytype apply {}' diff --git a/README.md b/README.md index b30c501dc..77e80c608 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # libtmux -libtmux is a [typed](https://docs.python.org/3/library/typing.html) python scripting library for tmux. You can use it to command and control tmux servers, -sessions, windows, and panes. It is the tool powering [tmuxp], a tmux workspace manager. +`libtmux` is a [typed](https://docs.python.org/3/library/typing.html) Python library that provides a wrapper for interacting programmatically with tmux, a terminal multiplexer. You can use it to manage tmux servers, +sessions, windows, and panes. Additionally, `libtmux` powers [tmuxp], a tmux workspace manager. [![Python Package](https://img.shields.io/pypi/v/libtmux.svg)](https://pypi.org/project/libtmux/) [![Docs](https://github.com/tmux-python/libtmux/workflows/docs/badge.svg)](https://libtmux.git-pull.com/) -[![Build Status](https://github.com/tmux-python/libtmux/workflows/tests/badge.svg)](https://github.com/tmux-python/tmux-python/actions?query=workflow%3A%22tests%22) +[![Build Status](https://github.com/tmux-python/libtmux/workflows/tests/badge.svg)](https://github.com/tmux-python/libtmux/actions?query=workflow%3A%22tests%22) [![Code Coverage](https://codecov.io/gh/tmux-python/libtmux/branch/master/graph/badge.svg)](https://codecov.io/gh/tmux-python/libtmux) [![License](https://img.shields.io/github/license/tmux-python/libtmux.svg)](https://github.com/tmux-python/libtmux/blob/master/LICENSE) @@ -53,9 +53,9 @@ Connect to a live tmux session: ```python >>> import libtmux ->>> server = libtmux.Server() ->>> server - +>>> svr = libtmux.Server() +>>> svr +Server(socket_path=/tmp/tmux-.../default) ``` Tip: You can also use [tmuxp]'s [`tmuxp shell`] to drop straight into your @@ -66,77 +66,146 @@ current tmux server / session / window pane. [ptpython]: https://github.com/prompt-toolkit/ptpython [ipython]: https://ipython.org/ +Run any tmux command, respective of context: + +Honors tmux socket name and path: + +```python +>>> server = Server(socket_name='libtmux_doctest') +>>> server.cmd('display-message', 'hello world') + +``` + +New session: + +```python +>>> server.cmd('new-session', '-d', '-P', '-F#{session_id}').stdout[0] +'$2' +``` + +```python +>>> session.cmd('new-window', '-P').stdout[0] +'libtmux...:2.0' +``` + +From raw command output, to a rich `Window` object (in practice and as shown +later, you'd use `Session.new_window()`): + +```python +>>> Window.from_window_id(window_id=session.cmd('new-window', '-P', '-F#{window_id}').stdout[0], server=session.server) +Window(@2 2:..., Session($1 libtmux_...)) +``` + +Create a pane from a window: + +```python +>>> window.cmd('split-window', '-P', '-F#{pane_id}').stdout[0] +'%2' +``` + +Raw output directly to a `Pane`: + +```python +>>> Pane.from_pane_id(pane_id=window.cmd('split-window', '-P', '-F#{pane_id}').stdout[0], server=window.server) +Pane(%... Window(@1 1:..., Session($1 libtmux_...))) +``` + List sessions: ```python ->>> server.list_sessions() -[Session($3 foo), Session($1 libtmux)] +>>> server.sessions +[Session($1 ...), Session($0 ...)] ``` -Find session: +Filter sessions by attribute: ```python ->>> server.get_by_id('$3') -Session($3 foo) +>>> server.sessions.filter(history_limit='2000') +[Session($1 ...), Session($0 ...)] ``` -Find session by dict lookup: +Direct lookup: ```python ->>> server.find_where({ "session_name": "foo" }) -Session($3 foo) +>>> server.sessions.get(session_id="$1") +Session($1 ...) ``` -Assign session to `session`: +Filter sessions: ```python ->>> session = server.find_where({ "session_name": "foo" }) +>>> server.sessions[0].rename_session('foo') +Session($1 foo) +>>> server.sessions.filter(session_name="foo") +[Session($1 foo)] +>>> server.sessions.get(session_name="foo") +Session($1 foo) ``` Control your session: ```python ->>> session.new_window(attach=False, window_name="ha in the bg") -Window(@8 2:ha in the bg, Session($3 foo)) ->>> session.kill_window("ha in") +>>> session +Session($1 ...) + +>>> session.rename_session('my-session') +Session($1 my-session) ``` Create new window in the background (don't switch to it): ```python ->>> w = session.new_window(attach=False, window_name="ha in the bg") -Window(@11 3:ha in the bg, Session($3 foo)) +>>> bg_window = session.new_window(attach=False, window_name="ha in the bg") +>>> bg_window +Window(@... 2:ha in the bg, Session($1 ...)) + +# Session can search the window +>>> session.windows.filter(window_name__startswith="ha") +[Window(@... 2:ha in the bg, Session($1 ...))] + +# Directly +>>> session.windows.get(window_name__startswith="ha") +Window(@... 2:ha in the bg, Session($1 ...)) + +# Clean up +>>> bg_window.kill() ``` Close window: ```python ->>> w.kill_window() +>>> w = session.active_window +>>> w.kill() ``` Grab remaining tmux window: ```python ->>> window = session.attached_window ->>> window.split_window(attach=False) -Pane(%23 Window(@10 1:bar, Session($3 foo))) +>>> window = session.active_window +>>> window.split(attach=False) +Pane(%2 Window(@1 1:... Session($1 ...))) ``` Rename window: ```python >>> window.rename_window('libtmuxower') -Window(@10 1:libtmuxower, Session($3 foo)) +Window(@1 1:libtmuxower, Session($1 ...)) ``` Split window (create a new pane): ```python ->>> pane = window.split_window() ->>> pane = window.split_window(attach=False) ->>> pane.select_pane() +>>> pane = window.split() +>>> pane = window.split(attach=False) +>>> pane.select() +Pane(%3 Window(@1 1:..., Session($1 ...))) >>> window = session.new_window(attach=False, window_name="test") ->>> pane = window.split_window(attach=False) +>>> window +Window(@2 2:test, Session($1 ...)) +>>> pane = window.split(attach=False) +>>> pane +Pane(%5 Window(@2 2:test, Session($1 ...))) ``` Type inside the pane (send key strokes): @@ -146,33 +215,35 @@ Type inside the pane (send key strokes): >>> pane.send_keys('echo hey', enter=False) >>> pane.enter() +Pane(%1 ...) ``` Grab the output of pane: ```python >>> pane.clear() # clear the pane ->>> pane.send_keys('cowsay hello') ->>> print('\n'.join(pane.cmd('capture-pane', '-p').stdout)) +Pane(%1 ...) +>>> pane.send_keys("cowsay 'hello'", enter=True) +>>> print('\n'.join(pane.cmd('capture-pane', '-p').stdout)) # doctest: +SKIP +$ cowsay 'hello' + _______ +< hello > + ------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || +... ``` - sh-3.2$ cowsay 'hello' - _______ - < hello > - ------- - \ ^__^ - \ (oo)\_______ - (__)\ )\/\ - ||----w | - || || - Traverse and navigate: ```python >>> pane.window -Window(@10 1:libtmuxower, Session($3 foo)) +Window(@1 1:..., Session($1 ...)) >>> pane.window.session -Session($3 foo) +Session($1 ...) ``` # Python support @@ -189,12 +260,12 @@ Your money will go directly to maintenance and development of the project. If you are an individual, feel free to give whatever feels right for the value you get out of the project. -See donation options at . +See donation options at . # Project details - tmux support: 1.8+ -- python support: >= 3.7, pypy, pypy3 +- python support: >= 3.9, pypy, pypy3 - Source: - Docs: - API: diff --git a/conftest.py b/conftest.py new file mode 100644 index 000000000..ada5aae3f --- /dev/null +++ b/conftest.py @@ -0,0 +1,75 @@ +"""Conftest.py (root-level). + +We keep this in root pytest fixtures in pytest's doctest plugin to be available, as well +as avoiding conftest.py from being included in the wheel, in addition to pytest_plugin +for pytester only being available via the root directory. + +See "pytest_plugins in non-top-level conftest files" in +https://docs.pytest.org/en/stable/deprecations.html +""" + +from __future__ import annotations + +import shutil +import typing as t + +import pytest +from _pytest.doctest import DoctestItem + +from libtmux.pane import Pane +from libtmux.pytest_plugin import USING_ZSH +from libtmux.server import Server +from libtmux.session import Session +from libtmux.window import Window + +if t.TYPE_CHECKING: + import pathlib + +pytest_plugins = ["pytester"] + + +@pytest.fixture(autouse=True) +def add_doctest_fixtures( + request: pytest.FixtureRequest, + doctest_namespace: dict[str, t.Any], +) -> None: + """Configure doctest fixtures for pytest-doctest.""" + if isinstance(request._pyfuncitem, DoctestItem) and shutil.which("tmux"): + request.getfixturevalue("set_home") + doctest_namespace["Server"] = Server + doctest_namespace["Session"] = Session + doctest_namespace["Window"] = Window + doctest_namespace["Pane"] = Pane + doctest_namespace["server"] = request.getfixturevalue("server") + doctest_namespace["Server"] = request.getfixturevalue("TestServer") + session: Session = request.getfixturevalue("session") + doctest_namespace["session"] = session + doctest_namespace["window"] = session.active_window + doctest_namespace["pane"] = session.active_pane + doctest_namespace["request"] = request + + +@pytest.fixture(autouse=True) +def set_home( + monkeypatch: pytest.MonkeyPatch, + user_path: pathlib.Path, +) -> None: + """Configure home directory for pytest tests.""" + monkeypatch.setenv("HOME", str(user_path)) + + +@pytest.fixture(autouse=True) +def setup_fn( + clear_env: None, +) -> None: + """Function-level test configuration fixtures for pytest.""" + + +@pytest.fixture(autouse=True, scope="session") +def setup_session( + request: pytest.FixtureRequest, + config_file: pathlib.Path, +) -> None: + """Session-level test configuration for pytest.""" + if USING_ZSH: + request.getfixturevalue("zshrc") diff --git a/docs/Makefile b/docs/Makefile index d0f8ec310..8501c103e 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -5,7 +5,7 @@ WATCH_FILES= find .. -type f -not -path '*/\.*' | grep -i '.*[.]\(rst\|md\)\$\|. # You can set these variables from the command line. SPHINXOPTS = -SPHINXBUILD = poetry run sphinx-build +SPHINXBUILD = uv run sphinx-build PAPER = BUILDDIR = _build @@ -181,8 +181,8 @@ dev: $(MAKE) -j watch serve start: - poetry run sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) --port ${HTTP_PORT} $(O) + uv run sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) --port ${HTTP_PORT} $(O) design: # This adds additional watch directories (for _static file changes) and disable incremental builds - poetry run sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) --port ${HTTP_PORT} --watch "." -a $(O) + uv run sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) --port ${HTTP_PORT} --watch "." -a $(O) diff --git a/docs/__init__.py b/docs/__init__.py new file mode 100644 index 000000000..3cea6d1af --- /dev/null +++ b/docs/__init__.py @@ -0,0 +1 @@ +"""Documentation for the libtmux package.""" diff --git a/docs/_static/img/libtmux.svg b/docs/_static/img/libtmux.svg index b31bf4999..30f9423a6 100644 --- a/docs/_static/img/libtmux.svg +++ b/docs/_static/img/libtmux.svg @@ -57,11 +57,11 @@ style="display:inline" transform="translate(-210.28959,-409.98108)"> Blue Sqaure Group object (Shape) + id="title966">Blue Square Group object (Shape) Yellow Sqaure Group object (Shape) + id="title962">Yellow Square Group object (Shape) Bottom bar object (Shape) Light Blue Sqaure Group object (Shape) + id="title964">Light Blue Square Group object (Shape) The book!

The Tao of tmux is available on Leanpub and Kindle (Amazon).

Read and browse the book for free on the web.

-Amazon Kindle +Amazon Kindle diff --git a/docs/_templates/sidebar/projects.html b/docs/_templates/sidebar/projects.html index 330d15930..7b46e0bce 100644 --- a/docs/_templates/sidebar/projects.html +++ b/docs/_templates/sidebar/projects.html @@ -1,7 +1,7 @@