Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a9e6404
feat: Add psycopg-inspired async-first architecture for libtmux
tony Nov 9, 2025
62f3e0e
common(cmd) AsyncTmuxCmd
tony Nov 9, 2025
93de623
Server,Session,Window,Pane: Add `.acmd`
tony Nov 9, 2025
0e75282
tests(async) Basic example
tony Nov 9, 2025
bdf1b4e
py(deps[dev]) Add `pytest-asyncio`
tony Nov 9, 2025
8f48199
feat: Integrate asyncio branch with psycopg-style architecture
tony Nov 9, 2025
7941cc2
test: Add comprehensive async tests with guaranteed isolation
tony Nov 9, 2025
3ee6eaa
py(deps) Bump `pytest-asyncio`
tony Nov 9, 2025
a635170
fix: Remove try/except around async fixtures
tony Nov 9, 2025
7f7204b
fix: Ensure server socket exists before testing error handling
tony Nov 9, 2025
26d818f
fix: Wrap async doctest in function for tmux_cmd_async
tony Nov 9, 2025
1986bb3
refactor: Remove manual session cleanup from async tests
tony Nov 9, 2025
967234f
docs: Phase 1 - Make async support discoverable (Quick Wins)
tony Nov 9, 2025
11132f8
Make example scripts self-contained and copy-pasteable
tony Nov 9, 2025
050fd0e
Add comprehensive async documentation
tony Nov 9, 2025
7ac451b
Expand common_async.py docstrings with dual pattern examples
tony Nov 9, 2025
ddc4254
fix: Correct async doctest examples for proper tmux isolation
tony Nov 9, 2025
d12ef7c
refactor: Convert SKIP'd doctests to executable code blocks in common…
tony Nov 9, 2025
3cc2f9f
test: Add verification tests for all docstring examples (Phase 2)
tony Nov 9, 2025
da1ae77
refactor: Convert common_async.py code blocks to executable doctests
tony Nov 9, 2025
efb76bc
refactor: Reorganize async tests into tests/asyncio/ directory
tony Nov 9, 2025
30c8cf7
test: Add comprehensive async environment variable tests
tony Nov 9, 2025
dc1c408
test: Add version checking edge case tests
tony Nov 9, 2025
87c858a
feat: make tmux_cmd_async awaitable for typing
tony Nov 9, 2025
455a9fa
chore(rebase): merge duplicate pytest markers + drop redundant pane defs
tony May 10, 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
436 changes: 235 additions & 201 deletions README.md

Large diffs are not rendered by default.

56 changes: 56 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@

from __future__ import annotations

import asyncio
import functools
import shutil
import typing as t

import pytest
import pytest_asyncio
from _pytest.doctest import DoctestItem

from libtmux._internal.control_mode import ControlMode
from libtmux.common_async import get_version, tmux_cmd_async
from libtmux.pane import Pane
from libtmux.pytest_plugin import USING_ZSH
from libtmux.server import Server
Expand Down Expand Up @@ -57,6 +60,11 @@ def add_doctest_fixtures(
)
doctest_namespace["monkeypatch"] = request.getfixturevalue("monkeypatch")

# Add async support for async doctests
doctest_namespace["asyncio"] = asyncio
doctest_namespace["tmux_cmd_async"] = tmux_cmd_async
doctest_namespace["get_version"] = get_version


@pytest.fixture(autouse=True)
def set_home(
Expand All @@ -82,3 +90,51 @@ def setup_session(
"""Session-level test configuration for pytest."""
if USING_ZSH:
request.getfixturevalue("zshrc")


# Async test fixtures
# These require pytest-asyncio to be installed


@pytest_asyncio.fixture
async def async_server(server: Server):
"""Async wrapper for sync server fixture.

Provides async context while using proven sync server isolation.
Server has unique socket name from libtmux_test{random}.

The sync server fixture creates a Server with:
- Unique socket name: libtmux_test{8-random-chars}
- Automatic cleanup via request.addfinalizer
- Complete isolation from developer's tmux sessions

This wrapper just ensures we're in an async context.
All cleanup is handled by the parent sync fixture.
"""
await asyncio.sleep(0) # Ensure in async context
yield server
# Cleanup handled by sync fixture's finalizer


@pytest_asyncio.fixture
async def async_test_server(TestServer: t.Callable[..., Server]):
"""Async wrapper for TestServer factory fixture.

Returns factory that creates servers with unique sockets.
Each call to factory() creates new isolated server.

The sync TestServer fixture creates a factory that:
- Generates unique socket names per call
- Tracks all created servers
- Cleans up all servers via request.addfinalizer

Usage in async tests:
server1 = async_test_server() # Creates server with unique socket
server2 = async_test_server() # Creates another with different socket

This wrapper just ensures we're in an async context.
All cleanup is handled by the parent sync fixture.
"""
await asyncio.sleep(0) # Ensure in async context
yield TestServer
# Cleanup handled by TestServer's finalizer
109 changes: 18 additions & 91 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,110 +1,37 @@
(index)=

# libtmux

Typed Python API for [tmux](https://github.com/tmux/tmux). Control
servers, sessions, windows, and panes as Python objects.

::::{grid} 1 1 3 3
:gutter: 2 2 3 3

:::{grid-item-card} Quickstart
:link: quickstart
:link-type: doc
Install and make your first API call in 5 minutes.
:::

:::{grid-item-card} Topics
:link: topics/index
:link-type: doc
Architecture, traversal, filtering, and automation patterns.
:::

:::{grid-item-card} API Reference
:link: api/index
:link-type: doc
Every public class, function, and exception.
:::

:::{grid-item-card} Testing
:link: api/testing/index
:link-type: doc
pytest plugin and test helpers for isolated tmux environments.
:::

:::{grid-item-card} Contributing
:link: project/index
:link-type: doc
Development setup, code style, release process.
:::

::::

## Install

```console
$ pip install libtmux
```

```console
$ uv add libtmux
```
---
hide-toc: true
---

Tip: libtmux is pre-1.0. Pin to a range: `libtmux>=0.55,<0.56`

See [Quickstart](quickstart.md) for all methods and first steps.

## At a glance

```python
import libtmux
(index)=

server = libtmux.Server()
session = server.sessions.get(session_name="my-project")
window = session.active_window
pane = window.split()
pane.send_keys("echo hello")
```
```{include} ../README.md

```
Server → Session → Window → Pane
```

Every level of the [tmux hierarchy](topics/architecture.md) is a typed
Python object with traversal, filtering, and command execution.
## Table of Contents

| Object | What it wraps |
|--------|---------------|
| {class}`~libtmux.server.Server` | tmux server / socket |
| {class}`~libtmux.session.Session` | tmux session |
| {class}`~libtmux.window.Window` | tmux window |
| {class}`~libtmux.pane.Pane` | tmux pane |

## Testing
:hidden:

libtmux ships a [pytest plugin](api/testing/pytest-plugin/index.md) with
isolated tmux fixtures:
```{toctree}
:maxdepth: 2

```python
def test_my_tool(session):
window = session.new_window(window_name="test")
pane = window.active_pane
pane.send_keys("echo hello")
assert window.window_name == "test"
quickstart
quickstart_async
about
topics/index
api/index
pytest-plugin/index
test-helpers/index
```

```{toctree}
:caption: Project
:hidden:

quickstart
topics/index
api/index
api/testing/index
developing
internals/index
project/index
history
migration
glossary
MCP <https://libtmux-mcp.git-pull.com>
GitHub <https://github.com/tmux-python/libtmux>
```
57 changes: 18 additions & 39 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ from inside a live tmux session.

## Requirements

- [tmux] 3.2a or newer
- [tmux]
- [pip] - for this handbook's examples

[tmux]: https://tmux.github.io/
Expand Down Expand Up @@ -44,15 +44,10 @@ the 4th beta release of `1.10.0` before general availability.
- [pipx]\:

```console
$ pipx install \
--suffix=@next \
--pip-args '\--pre' \
--force \
'libtmux'
$ pipx install --suffix=@next 'libtmux' --pip-args '\--pre' --force
// Usage: libtmux@next [command]
```

Usage: `libtmux@next [command]`

- [uv tool install][uv-tools]\:

```console
Expand Down Expand Up @@ -82,10 +77,7 @@ via trunk (can break easily):
- [pipx]\:

```console
$ pipx install \
--suffix=@master \
--force \
'libtmux @ git+https://github.com/tmux-python/libtmux.git@master'
$ pipx install --suffix=@master 'libtmux @ git+https://github.com/tmux-python/libtmux.git@master' --force
```

- [uv]\:
Expand Down Expand Up @@ -137,7 +129,7 @@ $ ptpython
```

```{module} libtmux
:no-index:

```

First, we can grab a {class}`Server`.
Expand Down Expand Up @@ -449,37 +441,23 @@ automatically sent, the leading space character prevents adding it to the user's
shell history. Omitting `enter=false` means the default behavior (sending the
command) is done, without needing to use `pane.enter()` after.

## Working with options
## Examples

libtmux provides a unified API for managing tmux options across Server, Session,
Window, and Pane objects.
Want to see more? Check out our example scripts:

### Getting options
- **[examples/async_demo.py]** - Async command execution with performance benchmarks
- **[examples/hybrid_async_demo.py]** - Both sync and async patterns working together
- **[More examples]** - Full examples directory on GitHub

```python
>>> server.show_option('buffer-limit')
50
For async-specific guides, see:

>>> window.show_options() # doctest: +ELLIPSIS
{...}
```

### Setting options

```python
>>> window.set_option('automatic-rename', False) # doctest: +ELLIPSIS
Window(@... ...)
- {doc}`/quickstart_async` - Async quickstart tutorial
- {doc}`/topics/async_programming` - Comprehensive async guide
- {doc}`/api/common_async` - Async API reference

>>> window.show_option('automatic-rename')
False

>>> window.unset_option('automatic-rename') # doctest: +ELLIPSIS
Window(@... ...)
```

:::{seealso}
See {ref}`options-and-hooks` for more details on options and hooks.
:::
[examples/async_demo.py]: https://github.com/tmux-python/libtmux/blob/master/examples/async_demo.py
[examples/hybrid_async_demo.py]: https://github.com/tmux-python/libtmux/blob/master/examples/hybrid_async_demo.py
[More examples]: https://github.com/tmux-python/libtmux/tree/master/examples

## Final notes

Expand All @@ -499,3 +477,4 @@ and our [test suite] (see {ref}`development`.)

[workspacebuilder.py]: https://github.com/tmux-python/libtmux/blob/master/libtmux/workspacebuilder.py
[test suite]: https://github.com/tmux-python/libtmux/tree/master/tests
[ptpython]: https://github.com/prompt-toolkit/ptpython
72 changes: 7 additions & 65 deletions docs/topics/index.md
Original file line number Diff line number Diff line change
@@ -1,72 +1,14 @@
# Topics

Explore libtmux's core functionalities and underlying principles at a high level, while providing essential context and detailed explanations to help you understand its design and usage.

::::{grid} 1 1 2 2
:gutter: 2 2 3 3

:::{grid-item-card} Architecture
:link: architecture
:link-type: doc
Module hierarchy, data flow, and internal identifiers.
:::

:::{grid-item-card} Traversal
:link: traversal
:link-type: doc
Navigate the Server, Session, Window, Pane hierarchy.
:::

:::{grid-item-card} Filtering
:link: filtering
:link-type: doc
Query and filter collections by attributes.
:::

:::{grid-item-card} Pane Interaction
:link: pane_interaction
:link-type: doc
Send keys, capture output, and interact with panes.
:::
---
orphan: true
---

:::{grid-item-card} Workspace Setup
:link: workspace_setup
:link-type: doc
Create sessions, windows, and panes programmatically.
:::

:::{grid-item-card} Automation Patterns
:link: automation_patterns
:link-type: doc
Common patterns for scripting and automation.
:::

:::{grid-item-card} Context Managers
:link: context_managers
:link-type: doc
Automatic cleanup with temporary sessions and windows.
:::

:::{grid-item-card} Options & Hooks
:link: options_and_hooks
:link-type: doc
Get and set tmux options and hooks.
:::
# Topics

::::
Explore libtmux’s core functionalities and underlying principles at a high level, while providing essential context and detailed explanations to help you understand its design and usage.

```{toctree}
:hidden:

architecture
configuration
design-decisions
public-vs-internal
traversal
filtering
pane_interaction
workspace_setup
automation_patterns
async_programming
context_managers
options_and_hooks
traversal
```
Loading
Loading