diff --git a/README.md b/README.md index 0944eea4e..778c218ef 100644 --- a/README.md +++ b/README.md @@ -230,12 +230,12 @@ Session($... ...) | libtmux object | tmux concept | Notes | |----------------|-----------------------------|--------------------------------| -| [`Server`](https://libtmux.git-pull.com/api/servers.html) | tmux server / socket | Entry point; owns sessions | -| [`Session`](https://libtmux.git-pull.com/api/sessions.html) | tmux session (`$0`, `$1`,...) | Owns windows | -| [`Window`](https://libtmux.git-pull.com/api/windows.html) | tmux window (`@1`, `@2`,...) | Owns panes | -| [`Pane`](https://libtmux.git-pull.com/api/panes.html) | tmux pane (`%1`, `%2`,...) | Where commands run | +| [`Server`](https://libtmux.git-pull.com/api/libtmux.server.html) | tmux server / socket | Entry point; owns sessions | +| [`Session`](https://libtmux.git-pull.com/api/libtmux.session.html) | tmux session (`$0`, `$1`,...) | Owns windows | +| [`Window`](https://libtmux.git-pull.com/api/libtmux.window.html) | tmux window (`@1`, `@2`,...) | Owns panes | +| [`Pane`](https://libtmux.git-pull.com/api/libtmux.pane.html) | tmux pane (`%1`, `%2`,...) | Where commands run | -Also available: [`Options`](https://libtmux.git-pull.com/api/options.html) and [`Hooks`](https://libtmux.git-pull.com/api/hooks.html) abstractions for tmux configuration. +Also available: [`Options`](https://libtmux.git-pull.com/api/libtmux.options.html) and [`Hooks`](https://libtmux.git-pull.com/api/libtmux.hooks.html) abstractions for tmux configuration. Collections are live and queryable: diff --git a/docs/about.md b/docs/about.md deleted file mode 100644 index 898d2fa4c..000000000 --- a/docs/about.md +++ /dev/null @@ -1,100 +0,0 @@ -(about)= - -# About - -:::{seealso} - -{ref}`api` - -::: - -```{currentmodule} libtmux - -``` - -libtmux is a [typed](https://docs.python.org/3/library/typing.html) [abstraction layer] for tmux. - -It builds upon the concept of targets `-t`, to direct commands against -individual sessions, windows and panes and `FORMATS`, template variables -exposed by tmux to describe their properties. Think of `-t` analogously -to [scope]. - -{class}`common.TmuxRelationalObject` acts as a container to connect the -relations of {class}`Server`, {class}`Session`, {class}`Window` and -{class}`Pane`. - -| Object | Child | Parent | -| ---------------- | ---------------- | ---------------- | -| {class}`Server` | {class}`Session` | None | -| {class}`Session` | {class}`Window` | {class}`Server` | -| {class}`Window` | {class}`Pane` | {class}`Session` | -| {class}`Pane` | None | {class}`Window` | - -Internally, tmux allows multiple servers to be run on a system. Each one -uses a socket. The server-client architecture is executed so cleanly, -many users don't think about it. tmux automatically connects to a default -socket name and location for you if none (`-L`, `-S`) is specified. -A server will be created automatically upon starting if none exists. - -A server can have multiple sessions. `Ctrl+a s` can be used to switch -between sessions running on the server. - -Sessions, windows and panes all have their own unique identifier for -internal purposes. {class}`common.TmuxMappingObject` will make use of the -unique identifiers (`session_id`, `window_id`, `pane_id`) to look -up the data stored in the {class}`Server` object. - -| Object | Prefix | Example | -| ---------------- | ------ | ----------------------------------------- | -| {class}`Server` | N/A | N/A, uses `socket-name` and `socket-path` | -| {class}`Session` | `$` | `$13` | -| {class}`Window` | `@` | `@3243` | -| {class}`Pane` | `%` | `%5433` | - -## Similarities to tmux and Pythonics - -libtmux was built in the spirit of understanding how tmux operates -and how python objects and tools can abstract the APIs in a pleasant way. - -libtmux uses `FORMATTERS` in tmux to give identity attributes to -{class}`Session`, {class}`Window` and {class}`Pane` objects. See -[format.c]. - -[format.c]: https://github.com/tmux/tmux/blob/master/format.c - -How is libtmux able to keep references to panes, windows and sessions? - -> Tmux has unique IDs for sessions, windows and panes. -> -> panes use `%`, such as `%1234` -> -> windows use `@`, such as `@2345` -> -> sessions use `$`, such as `$13` -> -> How is libtmux able to handle windows with no names? - -> Tmux provides `window_id` as a unique identifier. -> -> What is a {pane,window}\_index vs a {pane,window,session}\_id? - -> Pane index refers to the order of a pane on the screen. -> -> Window index refers to the number of the window in the session. -> -> To assert pane, window and session data, libtmux will use -> {meth}`Server.sessions()`, {meth}`Session.windows()`, -> {meth}`Window.panes()` to update objects. - -## Naming conventions - -Because this is a python abstraction and commands like `new-window` -have dashes (-) replaced with underscores (\_). - -## Reference - -- tmux docs -- tmux source code - -[abstraction layer]: http://en.wikipedia.org/wiki/Abstraction_layer -[scope]: https://en.wikipedia.org/wiki/Variable_(computer_science)#Scope_and_extent diff --git a/docs/api/compatibility.md b/docs/api/compatibility.md new file mode 100644 index 000000000..4dc5347e1 --- /dev/null +++ b/docs/api/compatibility.md @@ -0,0 +1,29 @@ +# Compatibility + +## Python + +- **Minimum**: Python 3.10 +- **Tested**: Python 3.10, 3.11, 3.12, 3.13 +- **Maximum**: Python < 4.0 + +## tmux + +- **Minimum**: tmux 3.2a +- **Tested**: latest stable tmux release +- libtmux uses tmux's format system and control mode -- older tmux versions + may lack required format variables + +## Platforms + +| Platform | Status | +|----------|--------| +| Linux | Fully supported | +| macOS | Fully supported | +| WSL / WSL2 | Supported (tmux runs inside WSL) | +| Windows (native) | Not supported (tmux does not run natively on Windows) | + +## Known Limitations + +- tmux must be running and accessible via the default socket or a specified socket +- Some operations require the tmux server to have at least one session +- Format string availability depends on tmux version diff --git a/docs/api/deprecations.md b/docs/api/deprecations.md new file mode 100644 index 000000000..631102b8c --- /dev/null +++ b/docs/api/deprecations.md @@ -0,0 +1,16 @@ +# Deprecations + +Active deprecations with timeline and migration paths. + +## Active Deprecations + + + +No active deprecations at this time. + +See [history](../history.md) for past changes and the +[migration guide](../migration.md) for upgrading between versions. + +## Deprecation Policy + +See [Public API -- Deprecation Process](public-api.md#deprecation-process). diff --git a/docs/api/index.md b/docs/api/index.md index 49c720a5c..a352d8cdc 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -4,16 +4,103 @@ # API Reference +libtmux's public API mirrors tmux's object hierarchy: + +``` +Server -> Session -> Window -> Pane +``` + +## Core Objects + +::::{grid} 2 +:gutter: 3 + +:::{grid-item-card} Server +:link: libtmux.server +:link-type: doc +Entry point. Manages sessions and executes raw tmux commands. +::: + +:::{grid-item-card} Session +:link: libtmux.session +:link-type: doc +Manages windows within a tmux session. +::: + +:::{grid-item-card} Window +:link: libtmux.window +:link-type: doc +Manages panes, layouts, and window operations. +::: + +:::{grid-item-card} Pane +:link: libtmux.pane +:link-type: doc +Terminal instance. Send keys and capture output. +::: + +:::: + +## Supporting Modules + +::::{grid} 3 +:gutter: 3 + +:::{grid-item-card} Common +:link: libtmux.common +:link-type: doc +Base classes and command execution. +::: + +:::{grid-item-card} Neo +:link: libtmux.neo +:link-type: doc +Dataclass-based query interface. +::: + +:::{grid-item-card} Options +:link: libtmux.options +:link-type: doc +tmux option get/set. +::: + +:::{grid-item-card} Hooks +:link: libtmux.hooks +:link-type: doc +tmux hook management. +::: + +:::{grid-item-card} Constants +:link: libtmux.constants +:link-type: doc +Format strings and constants. +::: + +:::{grid-item-card} Exceptions +:link: libtmux.exc +:link-type: doc +Exception hierarchy. +::: + +:::: + +## Test Utilities + +If you're testing code that uses libtmux, see the test helpers and pytest plugin: + +```{toctree} +:maxdepth: 1 + +test-helpers/index +pytest-plugin/index +``` + +## API Contract + ```{toctree} +:maxdepth: 1 -properties -servers -sessions -windows -panes -options -hooks -constants -common -exceptions +public-api +compatibility +deprecations ``` diff --git a/docs/api/common.md b/docs/api/libtmux.common.md similarity index 100% rename from docs/api/common.md rename to docs/api/libtmux.common.md diff --git a/docs/api/constants.md b/docs/api/libtmux.constants.md similarity index 100% rename from docs/api/constants.md rename to docs/api/libtmux.constants.md diff --git a/docs/api/exceptions.md b/docs/api/libtmux.exc.md similarity index 100% rename from docs/api/exceptions.md rename to docs/api/libtmux.exc.md diff --git a/docs/api/hooks.md b/docs/api/libtmux.hooks.md similarity index 100% rename from docs/api/hooks.md rename to docs/api/libtmux.hooks.md diff --git a/docs/api/properties.md b/docs/api/libtmux.neo.md similarity index 100% rename from docs/api/properties.md rename to docs/api/libtmux.neo.md diff --git a/docs/api/options.md b/docs/api/libtmux.options.md similarity index 100% rename from docs/api/options.md rename to docs/api/libtmux.options.md diff --git a/docs/api/panes.md b/docs/api/libtmux.pane.md similarity index 100% rename from docs/api/panes.md rename to docs/api/libtmux.pane.md diff --git a/docs/api/servers.md b/docs/api/libtmux.server.md similarity index 100% rename from docs/api/servers.md rename to docs/api/libtmux.server.md diff --git a/docs/api/sessions.md b/docs/api/libtmux.session.md similarity index 100% rename from docs/api/sessions.md rename to docs/api/libtmux.session.md diff --git a/docs/api/windows.md b/docs/api/libtmux.window.md similarity index 100% rename from docs/api/windows.md rename to docs/api/libtmux.window.md diff --git a/docs/api/public-api.md b/docs/api/public-api.md new file mode 100644 index 000000000..7fe1367db --- /dev/null +++ b/docs/api/public-api.md @@ -0,0 +1,57 @@ +# Public API + +## What Is Public + +Every module documented under [API Reference](index.md) is public API. +This includes: + +### Core Library + +| Module | Import Path | +|--------|-------------| +| Server | `from libtmux.server import Server` | +| Session | `from libtmux.session import Session` | +| Window | `from libtmux.window import Window` | +| Pane | `from libtmux.pane import Pane` | +| Common | `from libtmux.common import ...` | +| Neo | `from libtmux.neo import ...` | +| Options | `from libtmux.options import ...` | +| Hooks | `from libtmux.hooks import ...` | +| Constants | `from libtmux.constants import ...` | +| Exceptions | `from libtmux.exc import ...` | + +### Test Utilities + +| Module | Import Path | +|--------|-------------| +| Test helpers | `from libtmux.test import ...` | +| Pytest plugin | `libtmux.pytest_plugin` (auto-loaded) | + +## What Is Internal + +Modules under `libtmux._internal` and `libtmux._vendor` are **not public**. +They may change or be removed without notice between any release. + +Do not import from: +- `libtmux._internal.*` +- `libtmux._vendor.*` + +## Pre-1.0 Stability Policy + +libtmux is pre-1.0. This means: + +- **Minor versions** (0.x -> 0.y) may include breaking API changes +- **Patch versions** (0.x.y -> 0.x.z) are bug fixes only +- **Pin your dependency**: use `libtmux>=0.55,<0.56` or `libtmux~=0.55.0` + +Breaking changes are documented in the [changelog](../history.md) and +the [deprecations](deprecations.md) page before removal. + +## Deprecation Process + +Before removing or changing public API: + +1. A deprecation warning is added for at least one minor release +2. The change is documented in [deprecations](deprecations.md) +3. Migration guidance is provided +4. The old API is removed in a subsequent minor release diff --git a/docs/pytest-plugin/index.md b/docs/api/pytest-plugin/index.md similarity index 100% rename from docs/pytest-plugin/index.md rename to docs/api/pytest-plugin/index.md diff --git a/docs/test-helpers/constants.md b/docs/api/test-helpers/constants.md similarity index 100% rename from docs/test-helpers/constants.md rename to docs/api/test-helpers/constants.md diff --git a/docs/test-helpers/environment.md b/docs/api/test-helpers/environment.md similarity index 100% rename from docs/test-helpers/environment.md rename to docs/api/test-helpers/environment.md diff --git a/docs/test-helpers/index.md b/docs/api/test-helpers/index.md similarity index 100% rename from docs/test-helpers/index.md rename to docs/api/test-helpers/index.md diff --git a/docs/test-helpers/random.md b/docs/api/test-helpers/random.md similarity index 100% rename from docs/test-helpers/random.md rename to docs/api/test-helpers/random.md diff --git a/docs/test-helpers/retry.md b/docs/api/test-helpers/retry.md similarity index 100% rename from docs/test-helpers/retry.md rename to docs/api/test-helpers/retry.md diff --git a/docs/test-helpers/temporary.md b/docs/api/test-helpers/temporary.md similarity index 100% rename from docs/test-helpers/temporary.md rename to docs/api/test-helpers/temporary.md diff --git a/docs/conf.py b/docs/conf.py index 2c634281b..fb04d0d28 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -42,6 +42,7 @@ "sphinxext.rediraffe", "myst_parser", "linkify_issues", + "sphinx_design", ] myst_enable_extensions = [ @@ -52,6 +53,8 @@ "linkify", ] +myst_heading_anchors = 4 + templates_path = ["_templates"] source_suffix = {".rst": "restructuredtext", ".md": "markdown"} diff --git a/docs/index.md b/docs/index.md index 341532ee9..9f1983424 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,30 +2,100 @@ # libtmux -```{include} ../README.md -:start-after: +Typed Python API for [tmux](https://github.com/tmux/tmux). Control +servers, sessions, windows, and panes as Python objects. + +::::{grid} 2 +:gutter: 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} Contributing +:link: project/index +:link-type: doc +Development setup, code style, release process. +::: + +:::: + +## Install + +```console +$ pip install libtmux ``` -```{toctree} -:maxdepth: 2 -:hidden: +```console +$ uv add libtmux +``` -quickstart -about -topics/index -api/index -pytest-plugin/index -test-helpers/index +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 + +server = libtmux.Server() +session = server.sessions.get(session_name="my-project") +window = session.active_window +pane = window.split() +pane.send_keys("echo hello") +``` + +``` +Server → Session → Window → Pane +``` + +Every level of the [tmux hierarchy](topics/architecture.md) is a typed +Python object with traversal, filtering, and command execution. + +| 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 + +libtmux ships a [pytest plugin](api/pytest-plugin/index.md) with +isolated tmux fixtures: + +```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" ``` ```{toctree} -:caption: Project :hidden: -developing +quickstart +topics/index +api/index internals/index +project/index history migration glossary -GitHub ``` diff --git a/docs/internals/constants.md b/docs/internals/api/libtmux._internal.constants.md similarity index 100% rename from docs/internals/constants.md rename to docs/internals/api/libtmux._internal.constants.md diff --git a/docs/internals/dataclasses.md b/docs/internals/api/libtmux._internal.dataclasses.md similarity index 100% rename from docs/internals/dataclasses.md rename to docs/internals/api/libtmux._internal.dataclasses.md diff --git a/docs/internals/query_list.md b/docs/internals/api/libtmux._internal.query_list.md similarity index 100% rename from docs/internals/query_list.md rename to docs/internals/api/libtmux._internal.query_list.md diff --git a/docs/internals/sparse_array.md b/docs/internals/api/libtmux._internal.sparse_array.md similarity index 100% rename from docs/internals/sparse_array.md rename to docs/internals/api/libtmux._internal.sparse_array.md diff --git a/docs/internals/index.md b/docs/internals/index.md index 0d19d3763..82fe50804 100644 --- a/docs/internals/index.md +++ b/docs/internals/index.md @@ -2,17 +2,19 @@ # Internals -:::{warning} -Be careful with these! Internal APIs are **not** covered by version policies. They can break or be removed between minor versions! +:::{danger} +**No stability guarantee.** Internal APIs are **not** covered by version +policies. They can break or be removed between any minor versions without +notice. If you need an internal API stabilized please [file an issue](https://github.com/tmux-python/libtmux/issues). ::: ```{toctree} -dataclasses -query_list -constants -sparse_array +api/libtmux._internal.dataclasses +api/libtmux._internal.query_list +api/libtmux._internal.constants +api/libtmux._internal.sparse_array ``` ## Environmental variables diff --git a/docs/project/code-style.md b/docs/project/code-style.md new file mode 100644 index 000000000..30344fe77 --- /dev/null +++ b/docs/project/code-style.md @@ -0,0 +1,33 @@ +# Code Style + +## Formatting + +libtmux uses [ruff](https://github.com/astral-sh/ruff) for both linting and formatting. + +```console +$ uv run ruff format . +``` + +```console +$ uv run ruff check . --fix --show-fixes +``` + +## Type Checking + +Strict [mypy](https://mypy-lang.org/) is enforced across `src/` and `tests/`. + +```console +$ uv run mypy +``` + +## Docstrings + +All public functions and methods use NumPy-style docstrings. See the +[NumPy docstring guide](https://numpydoc.readthedocs.io/en/latest/format.html). + +## Imports + +- Standard library: namespace imports (`import pathlib`, not `from pathlib import Path`) + - Exception: `from dataclasses import dataclass, field` +- Typing: `import typing as t`, access via `t.Optional`, `t.NamedTuple`, etc. +- All files: `from __future__ import annotations` diff --git a/docs/developing.md b/docs/project/contributing.md similarity index 100% rename from docs/developing.md rename to docs/project/contributing.md diff --git a/docs/project/index.md b/docs/project/index.md new file mode 100644 index 000000000..8c37a15d8 --- /dev/null +++ b/docs/project/index.md @@ -0,0 +1,36 @@ +(project)= + +# Project + +Information for contributors and maintainers. + +::::{grid} 2 +:gutter: 3 + +:::{grid-item-card} Contributing +:link: contributing +:link-type: doc +Development setup, running tests, submitting PRs. +::: + +:::{grid-item-card} Code Style +:link: code-style +:link-type: doc +Ruff, mypy, NumPy docstrings, import conventions. +::: + +:::{grid-item-card} Releasing +:link: releasing +:link-type: doc +Release checklist and version policy. +::: + +:::: + +```{toctree} +:hidden: + +contributing +code-style +releasing +``` diff --git a/docs/project/releasing.md b/docs/project/releasing.md new file mode 100644 index 000000000..5aa55dadf --- /dev/null +++ b/docs/project/releasing.md @@ -0,0 +1,55 @@ +# Releasing + +## Version Policy + +libtmux is pre-1.0. Minor version bumps may include breaking API changes. +Users should pin to `>=0.x,<0.y`. + +## Release Process + +Releases are triggered by git tags and published to PyPI via OIDC trusted publishing. + +1. Update `CHANGES` with the release notes + +2. Bump version in `src/libtmux/__about__.py` + +3. Commit: + + ```console + $ git commit -m "libtmux " + ``` + +4. Tag: + + ```console + $ git tag v + ``` + +5. Push: + + ```console + $ git push && git push --tags + ``` + +6. CI builds and publishes to PyPI automatically via trusted publishing + +## Changelog Format + +The `CHANGES` file uses this format: + +```text +libtmux () +-------------------------- + +### What's new + +- Description of feature (#issue) + +### Bug fixes + +- Description of fix (#issue) + +### Breaking changes + +- Description of break, migration path (#issue) +``` diff --git a/docs/redirects.txt b/docs/redirects.txt index afff787ad..dd952340d 100644 --- a/docs/redirects.txt +++ b/docs/redirects.txt @@ -16,3 +16,26 @@ "reference/sessions.md" "api/sessions.md" "reference/windows.md" "api/windows.md" "pytest-plugin/test.md" "test-helpers/index.md" +"api/servers.md" "api/libtmux.server.md" +"api/sessions.md" "api/libtmux.session.md" +"api/windows.md" "api/libtmux.window.md" +"api/panes.md" "api/libtmux.pane.md" +"api/options.md" "api/libtmux.options.md" +"api/hooks.md" "api/libtmux.hooks.md" +"api/constants.md" "api/libtmux.constants.md" +"api/common.md" "api/libtmux.common.md" +"api/properties.md" "api/libtmux.neo.md" +"api/exceptions.md" "api/libtmux.exc.md" +"test-helpers/index.md" "api/test-helpers/index.md" +"test-helpers/constants.md" "api/test-helpers/constants.md" +"test-helpers/environment.md" "api/test-helpers/environment.md" +"test-helpers/temporary.md" "api/test-helpers/temporary.md" +"test-helpers/random.md" "api/test-helpers/random.md" +"test-helpers/retry.md" "api/test-helpers/retry.md" +"pytest-plugin/index.md" "api/pytest-plugin/index.md" +"developing.md" "project/contributing.md" +"internals/dataclasses.md" "internals/api/libtmux._internal.dataclasses.md" +"internals/constants.md" "internals/api/libtmux._internal.constants.md" +"internals/query_list.md" "internals/api/libtmux._internal.query_list.md" +"internals/sparse_array.md" "internals/api/libtmux._internal.sparse_array.md" +"about.md" "topics/architecture.md" diff --git a/docs/topics/architecture.md b/docs/topics/architecture.md new file mode 100644 index 000000000..df25834d8 --- /dev/null +++ b/docs/topics/architecture.md @@ -0,0 +1,85 @@ +(about)= + +# Architecture + +libtmux is a [typed](https://docs.python.org/3/library/typing.html) +abstraction layer for tmux. It builds upon tmux's concept of targets +(`-t`) to direct commands against individual sessions, windows, and panes, +and `FORMATS` — template variables tmux exposes to describe object +properties. + +## Object Hierarchy + +libtmux mirrors tmux's object hierarchy as a typed Python ORM: + +``` +Server +└── Session + └── Window + └── Pane +``` + +| Object | Child | Parent | +|--------|-------|--------| +| {class}`~libtmux.server.Server` | {class}`~libtmux.session.Session` | None | +| {class}`~libtmux.session.Session` | {class}`~libtmux.window.Window` | {class}`~libtmux.server.Server` | +| {class}`~libtmux.window.Window` | {class}`~libtmux.pane.Pane` | {class}`~libtmux.session.Session` | +| {class}`~libtmux.pane.Pane` | None | {class}`~libtmux.window.Window` | + +{class}`~libtmux.common.TmuxRelationalObject` acts as the base container +connecting these relationships. + +## Internal Identifiers + +tmux assigns unique IDs to sessions, windows, and panes. libtmux uses +these — via {class}`~libtmux.common.TmuxMappingObject` — to track objects +reliably across state refreshes. + +| Object | Prefix | Example | +|--------|--------|---------| +| {class}`~libtmux.server.Server` | N/A | Uses `socket-name` / `socket-path` | +| {class}`~libtmux.session.Session` | `$` | `$13` | +| {class}`~libtmux.window.Window` | `@` | `@3243` | +| {class}`~libtmux.pane.Pane` | `%` | `%5433` | + +## Core Objects + +Each level wraps tmux commands and format queries: + +- {class}`~libtmux.server.Server` — entry point, manages sessions, executes raw tmux commands +- {class}`~libtmux.session.Session` — manages windows within a session +- {class}`~libtmux.window.Window` — manages panes, handles layouts +- {class}`~libtmux.pane.Pane` — terminal instance, sends keys and captures output + +## Data Flow + +1. User creates a `Server` (connects to a running tmux server) +2. Queries use tmux format strings ({mod}`libtmux.constants`) to fetch state +3. Results are parsed into typed Python objects +4. Mutations dispatch tmux commands via the `cmd()` method +5. Objects refresh state from tmux on demand + +## Module Map + +| Module | Role | +|--------|------| +| {mod}`libtmux.server` | Server connection and session management | +| {mod}`libtmux.session` | Session operations | +| {mod}`libtmux.window` | Window operations and pane management | +| {mod}`libtmux.pane` | Pane I/O and capture | +| {mod}`libtmux.common` | Base classes, command execution | +| {mod}`libtmux.neo` | Modern dataclass-based query interface | +| {mod}`libtmux.constants` | Format string constants | +| {mod}`libtmux.options` | tmux option get/set | +| {mod}`libtmux.hooks` | tmux hook management | +| {mod}`libtmux.exc` | Exception hierarchy | + +## Naming Conventions + +tmux commands use dashes (`new-window`). libtmux replaces these with +underscores (`new_window`) to follow Python naming conventions. + +## References + +- [tmux man page](https://man.openbsd.org/tmux.1) +- [tmux source code](https://github.com/tmux/tmux) diff --git a/docs/topics/configuration.md b/docs/topics/configuration.md new file mode 100644 index 000000000..ec51b70ae --- /dev/null +++ b/docs/topics/configuration.md @@ -0,0 +1,17 @@ +# Configuration + +## Environment Variables + +libtmux itself does not read environment variables for configuration. +All configuration is done programmatically through the Python API. + +The tmux server libtmux connects to may be influenced by standard tmux +environment variables (`TMUX`, `TMUX_TMPDIR`). + +## Format Strings + +libtmux uses tmux's format system to query state. Format constants are +defined in {mod}`libtmux.formats` and used internally by all object types. + +See the [tmux man page](http://man.openbsd.org/OpenBSD-current/man1/tmux.1) +for the full list of available formats. diff --git a/docs/topics/design-decisions.md b/docs/topics/design-decisions.md new file mode 100644 index 000000000..700e5e4ab --- /dev/null +++ b/docs/topics/design-decisions.md @@ -0,0 +1,42 @@ +# Design Decisions + +## Why ORM-Style Objects + +tmux organizes terminals in a strict hierarchy: Server → Session → Window → Pane. Each level owns the next. libtmux mirrors this with Python objects that maintain the same parent-child relationships. + +The alternative — a flat command-builder API (`tmux("new-session", "-s", "foo")`) — loses the relational structure. You'd have to track which windows belong to which session manually. The ORM approach lets you write `session.windows` and get a live, filterable collection. + +## Why Format Strings + +tmux exposes object properties through its format system (`-F` flags). For example, `tmux list-sessions -F '#{session_id}:#{session_name}'` returns structured data. + +libtmux uses this instead of parsing human-readable `tmux ls` output because: + +- **Stability**: format variables are part of tmux's documented interface +- **Precision**: no regex fragility from parsing prose output +- **Completeness**: formats expose properties (like `session_id`) that don't appear in default output + +Format constants are defined in {mod}`libtmux.formats`. + +## Why Dataclasses in neo.py + +{mod}`libtmux.neo` provides a modern dataclass-based interface alongside the legacy dict-style objects. The motivation: + +- **Type safety**: dataclass fields have declared types, enabling mypy checks and IDE completion +- **Predictability**: attribute access (`session.session_name`) instead of dict access (`session["session_name"]`) +- **Migration path**: the two interfaces coexist, allowing gradual adoption without breaking existing code + +## Pre-1.0 API Evolution + +libtmux is pre-1.0. This is a deliberate choice — the API is still maturing. What this means in practice: + +- **Minor versions** (0.x → 0.y) may include breaking changes +- **Patch versions** (0.x.y → 0.x.z) are bug fixes only +- **Pin your dependency**: use `libtmux>=0.55,<0.56` or `libtmux~=0.55.0` + +Breaking changes always get: +1. A deprecation warning for at least one minor release +2. Documentation in the [changelog](../history.md) and [deprecations](../api/deprecations.md) +3. Migration guidance + +See [Public API](../api/public-api.md) for the stability contract. diff --git a/docs/topics/index.md b/docs/topics/index.md index f22e7f81b..6a2c4525a 100644 --- a/docs/topics/index.md +++ b/docs/topics/index.md @@ -1,13 +1,67 @@ ---- -orphan: true ---- - # 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} 2 +:gutter: 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. +::: + +:::{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. +::: + +:::: + ```{toctree} +:hidden: +architecture +configuration +design-decisions +public-vs-internal traversal filtering pane_interaction diff --git a/docs/topics/public-vs-internal.md b/docs/topics/public-vs-internal.md new file mode 100644 index 000000000..dd93722a4 --- /dev/null +++ b/docs/topics/public-vs-internal.md @@ -0,0 +1,48 @@ +# Public vs Internal API + +## The Boundary + +libtmux draws a clear line between public and internal code: + +| Import path | Status | Stability | +|-------------|--------|-----------| +| `libtmux.*` | Public | Covered by [deprecation policy](../api/public-api.md) | +| `libtmux._internal.*` | Internal | No guarantee — may break between any release | +| `libtmux._vendor.*` | Vendored | Not part of the API at all | + +If you can import it without a leading underscore in the module path, it's public. + +## Why the Split + +Internal modules exist so the library can iterate freely on implementation details without breaking downstream users. A refactor of `libtmux._internal.query_list` doesn't require a deprecation cycle — it's explicitly not part of the contract. + +This separation also keeps the public API surface intentionally small. Every public module is a commitment to maintain. Internal modules earn promotion through proven stability and user demand. + +## What `_internal/` Contains + +The `_internal` package holds implementation details that support the public API: + +- **`query_list`** — the filtering engine behind `.filter()` and `.get()` on collections +- **`dataclasses`** — base dataclass utilities used by the ORM objects +- **`constants`** — internal constants not meaningful to end users +- **`types`** — type aliases used across the codebase + +These are documented in [Internals](../internals/index.md) for contributors, but downstream projects should not import from them. + +## What `_vendor/` Contains + +The `_vendor` package holds vendored third-party code — copies of external libraries included directly to avoid adding dependencies. This code is not written by the libtmux authors and is not part of the API. + +## How Internal APIs Get Promoted + +1. **Internal**: lives in `_internal/`, no stability promise +2. **Experimental**: documented, usable, but explicitly marked as subject to change +3. **Public**: moved to a top-level module, covered by the deprecation policy + +Promotion happens when an internal API proves stable across multiple releases and users request it. If you depend on an internal API, [file an issue](https://github.com/tmux-python/libtmux/issues) — that signal helps prioritize promotion. + +## Reference + +- [Public API](../api/public-api.md) — the authoritative list of what's stable +- [Compatibility](../api/compatibility.md) — platform and version support +- [Deprecations](../api/deprecations.md) — what's changing diff --git a/pyproject.toml b/pyproject.toml index 84c73e066..b1467939b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,17 +52,18 @@ Changes = "https://github.com/tmux-python/libtmux/blob/master/CHANGES" [dependency-groups] dev = [ # Docs - "sphinx<9", - "furo", - "gp-libs", - "sphinx-autobuild", - "sphinx-autodoc-typehints", - "sphinx-inline-tabs", - "sphinxext-opengraph", - "sphinx-copybutton", - "sphinxext-rediraffe", - "myst-parser", - "linkify-it-py", + "sphinx<9", # https://www.sphinx-doc.org/ + "furo", # https://pradyunsg.me/furo/ + "gp-libs", # https://gp-libs.git-pull.com/ + "sphinx-autobuild", # https://sphinx-extensions.readthedocs.io/en/latest/sphinx-autobuild.html + "sphinx-autodoc-typehints", # https://sphinx-autodoc-typehints.readthedocs.io/ + "sphinx-inline-tabs", # https://sphinx-inline-tabs.readthedocs.io/ + "sphinxext-opengraph", # https://sphinxext-opengraph.readthedocs.io/ + "sphinx-copybutton", # https://sphinx-copybutton.readthedocs.io/ + "sphinxext-rediraffe", # https://sphinxext-rediraffe.readthedocs.io/ + "sphinx-design", # https://sphinx-design.readthedocs.io/ + "myst-parser", # https://myst-parser.readthedocs.io/ + "linkify-it-py", # https://github.com/tsutsu3/linkify-it-py # Testing "typing-extensions; python_version < '3.11'", "gp-libs", @@ -81,17 +82,18 @@ dev = [ ] docs = [ - "sphinx<9", - "furo", - "gp-libs", - "sphinx-autobuild", - "sphinx-autodoc-typehints", - "sphinx-inline-tabs", - "sphinxext-opengraph", - "sphinx-copybutton", - "sphinxext-rediraffe", - "myst-parser", - "linkify-it-py", + "sphinx<9", # https://www.sphinx-doc.org/ + "furo", # https://pradyunsg.me/furo/ + "gp-libs", # https://gp-libs.git-pull.com/ + "sphinx-autobuild", # https://sphinx-extensions.readthedocs.io/en/latest/sphinx-autobuild.html + "sphinx-autodoc-typehints", # https://sphinx-autodoc-typehints.readthedocs.io/ + "sphinx-inline-tabs", # https://sphinx-inline-tabs.readthedocs.io/ + "sphinxext-opengraph", # https://sphinxext-opengraph.readthedocs.io/ + "sphinx-copybutton", # https://sphinx-copybutton.readthedocs.io/ + "sphinxext-rediraffe", # https://sphinxext-rediraffe.readthedocs.io/ + "sphinx-design", # https://sphinx-design.readthedocs.io/ + "myst-parser", # https://myst-parser.readthedocs.io/ + "linkify-it-py", # https://github.com/tsutsu3/linkify-it-py ] testing = [ "typing-extensions; python_version < '3.11'", diff --git a/uv.lock b/uv.lock index 79d76aad3..e72e68a0b 100644 --- a/uv.lock +++ b/uv.lock @@ -558,6 +558,8 @@ dev = [ { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "sphinx-autodoc-typehints", version = "3.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-copybutton" }, + { name = "sphinx-design", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx-design", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-inline-tabs" }, { name = "sphinxext-opengraph" }, { name = "sphinxext-rediraffe" }, @@ -576,6 +578,8 @@ docs = [ { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "sphinx-autodoc-typehints", version = "3.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-copybutton" }, + { name = "sphinx-design", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx-design", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-inline-tabs" }, { name = "sphinxext-opengraph" }, { name = "sphinxext-rediraffe" }, @@ -621,6 +625,7 @@ dev = [ { name = "sphinx-autobuild" }, { name = "sphinx-autodoc-typehints" }, { name = "sphinx-copybutton" }, + { name = "sphinx-design" }, { name = "sphinx-inline-tabs" }, { name = "sphinxext-opengraph" }, { name = "sphinxext-rediraffe" }, @@ -635,6 +640,7 @@ docs = [ { name = "sphinx-autobuild" }, { name = "sphinx-autodoc-typehints" }, { name = "sphinx-copybutton" }, + { name = "sphinx-design" }, { name = "sphinx-inline-tabs" }, { name = "sphinxext-opengraph" }, { name = "sphinxext-rediraffe" }, @@ -1322,6 +1328,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, ] +[[package]] +name = "sphinx-design" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/69/b34e0cb5336f09c6866d53b4a19d76c227cdec1bbc7ac4de63ca7d58c9c7/sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632", size = 2193689, upload-time = "2024-08-02T13:48:44.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/43/65c0acbd8cc6f50195a3a1fc195c404988b15c67090e73c7a41a9f57d6bd/sphinx_design-0.6.1-py3-none-any.whl", hash = "sha256:b11f37db1a802a183d61b159d9a202314d4d2fe29c163437001324fe2f19549c", size = 2215338, upload-time = "2024-08-02T13:48:42.106Z" }, +] + +[[package]] +name = "sphinx-design" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/7b/804f311da4663a4aecc6cf7abd83443f3d4ded970826d0c958edc77d4527/sphinx_design-0.7.0.tar.gz", hash = "sha256:d2a3f5b19c24b916adb52f97c5f00efab4009ca337812001109084a740ec9b7a", size = 2203582, upload-time = "2026-01-19T13:12:53.297Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/cf/45dd359f6ca0c3762ce0490f681da242f0530c49c81050c035c016bfdd3a/sphinx_design-0.7.0-py3-none-any.whl", hash = "sha256:f82bf179951d58f55dca78ab3706aeafa496b741a91b1911d371441127d64282", size = 2220350, upload-time = "2026-01-19T13:12:51.077Z" }, +] + [[package]] name = "sphinx-inline-tabs" version = "2025.12.21.14" @@ -1417,15 +1454,15 @@ wheels = [ [[package]] name = "starlette" -version = "0.52.1" +version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" }, ] [[package]]