From be3b881e12e7c330514c37745286976abe931f3f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Wed, 28 Oct 2026 19:51:39 -0500 Subject: [PATCH 1/5] docs(fix[install]): Use library environments why: Library users need imports in their active Python environment. what: - Replace application-only installation routes with pip and uv - Show valid PEP 621 dependencies and tested-series bounds - Keep development installation in the quickstart --- CHANGES | 38 ++----------------------- README.md | 30 ++++++++++---------- docs/quickstart.md | 71 ++++++++++++++++------------------------------ 3 files changed, 42 insertions(+), 97 deletions(-) diff --git a/CHANGES b/CHANGES index 7a691b0cc9..dfc941aed0 100644 --- a/CHANGES +++ b/CHANGES @@ -1,41 +1,7 @@ # Changelog -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/): - -```console -$ pip install --user --upgrade --pre libtmux -``` - -[pipx](https://pypa.github.io/pipx/docs/): - -```console -$ pipx install \ - --suffix=@next \ - --pip-args '\--pre' \ - --force \ - 'libtmux' -``` - -Run the suffixed executable as: - -```console -$ libtmux@next [command] -``` - -[uv](https://docs.astral.sh/uv/): - -```console -$ uv add libtmux --prerelease allow -``` - -[uvx](https://docs.astral.sh/uv/guides/tools/): - -```console -$ uvx --from 'libtmux' --prerelease allow python -``` +For development installation, see +[development releases](https://libtmux.git-pull.com/quickstart/#developmental-releases). ## libtmux 0.63.x (Yet to be released) diff --git a/README.md b/README.md index bb59d7e07a..3f5310a8b5 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ libtmux is a typed Python API over [tmux], the terminal multiplexer. Stop shelli ## Requirements & support - tmux: >= 3.2a -- Python: >= 3.10 (CPython and PyPy) +- Python: >= 3.10, < 4.0 (CPython and PyPy) Maintenance-only backports (no new fixes): @@ -39,46 +39,46 @@ Maintenance-only backports (no new fixes): ## 📦 Installation -Release: +Install libtmux into the Python environment that will import it: ```console -$ pip install libtmux +$ python -m pip install libtmux ``` -With pipx: - -```console -$ pipx install libtmux -``` - -With uv / uvx: +With uv in a project: ```console $ uv add libtmux ``` +For an ad hoc Python REPL: + ```console -$ uvx --from "libtmux" python +$ uv run \ + --with libtmux \ + python ``` From the main branch (bleeding edge): ```console -$ pip install 'git+https://github.com/tmux-python/libtmux.git' +$ python -m pip install 'git+https://github.com/tmux-python/libtmux.git' ``` -Tip: libtmux is pre-1.0. Pin a range in projects to avoid surprises: +libtmux is pre-1.0: minor releases may change the API. Pin the minor series +you have tested; for example, to stay on 0.62.x: requirements.txt: ```ini -libtmux==0.50.* +libtmux>=0.62,<0.63 ``` pyproject.toml: ```toml -libtmux = "0.50.*" +[project] +dependencies = ["libtmux>=0.62,<0.63"] ``` ## 🚀 Quickstart diff --git a/docs/quickstart.md b/docs/quickstart.md index b8f9188493..8e879ebd3b 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -21,86 +21,65 @@ from inside a live tmux session. ## Installation -Next, ensure `libtmux` is installed: +Install `libtmux` into your project's Python environment: ```console -$ pip install --user libtmux +$ python -m pip install libtmux ``` (developmental-releases)= ### Developmental releases -New versions of libtmux are published to [PyPI] as alpha, beta, or release -candidates. In their versions you will see notifications like `a1`, `b1`, and -`rc1`, respectively. `1.10.0b4` would mean the 4th beta release of `1.10.0` -before general availability. +Prerelease versions on [PyPI] use suffixes such as `a1`, `b1`, and `rc1` +for alpha, beta, and release candidates. Opt into them with your package +manager: -- [pip]\: +- [pip]: ```console - $ pip install --user --upgrade --pre libtmux + $ python -m pip install \ + --upgrade \ + --pre \ + libtmux ``` -- [pipx]\: +- [uv] in a project: ```console - $ pipx install \ - --suffix=@next \ - --pip-args '\--pre' \ - --force \ - 'libtmux' + $ uv add \ + --prerelease allow \ + libtmux ``` - Usage: `libtmux@next [command]` - -- [uv tool install][uv-tools]\: - - ```console - $ uv tool install --prerelease=allow libtmux - ``` - -- [uv]\: - - ```console - $ uv add libtmux --prerelease allow - ``` - -- [uvx]\: +- An ad hoc Python REPL with [uv]: ```console - $ uvx --from 'libtmux' --prerelease allow python + $ uv run \ + --prerelease allow \ + --with libtmux \ + python ``` -via trunk (can break easily): +To install unreleased changes from trunk: -- [pip]\: +- [pip]: ```console - $ pip install --user -e git+https://github.com/tmux-python/libtmux.git#egg=libtmux - ``` - -- [pipx]\: - - ```console - $ pipx install \ - --suffix=@master \ - --force \ + $ python -m pip install \ 'libtmux @ git+https://github.com/tmux-python/libtmux.git@master' ``` -- [uv]\: +- [uv] in a project: ```console - $ uv tool install libtmux --from git+https://github.com/tmux-python/libtmux.git + $ uv add 'libtmux @ git+https://github.com/tmux-python/libtmux.git@master' ``` [pip]: https://pip.pypa.io/en/stable/ -[pipx]: https://pypa.github.io/pipx/docs/ [PyPI]: https://pypi.org/project/libtmux/ [uv]: https://docs.astral.sh/uv/ -[uv-tools]: https://docs.astral.sh/uv/concepts/tools/ -[uvx]: https://docs.astral.sh/uv/guides/tools/ + [ptpython]: https://github.com/prompt-toolkit/ptpython ## Start a tmux session From cc2bb59e34ed18aea63054f7cb1dc33a355e3572 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Wed, 28 Oct 2026 19:51:39 -0500 Subject: [PATCH 2/5] docs(fix[fixtures]): Make example setup explicit why: Repository fixtures hid prerequisites and cleanup ownership. what: - Add a fixture-free README example on a temporary socket - Explain requested fixtures and downstream doctest setup - Bound server context cleanup to resources the caller owns --- README.md | 21 +++++++++++++++++++++ docs/api/testing/pytest-plugin/fixtures.md | 5 +++-- docs/api/testing/pytest-plugin/usage.md | 10 ++++++---- docs/topics/context_managers.md | 10 ++++++++++ 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3f5310a8b5..6e5148b0f1 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,27 @@ dependencies = ["libtmux>=0.62,<0.63"] ## 🚀 Quickstart +Create a server on a temporary socket. The context managers stop the server +and remove the temporary directory when the block exits: + +```python +>>> import tempfile +>>> import libtmux +>>> with tempfile.TemporaryDirectory() as directory: +... with libtmux.Server(socket_path=f"{directory}/tmux.sock") as server: +... session = server.new_session(session_name="demo") +... window = session.active_window +... pane = window.active_pane +... pane.send_keys("echo hello") +... print(session.session_name) +demo +``` + +The shorter reference examples below use `server`, `session`, `window`, +`pane`, and imported classes supplied by this repository's doctest setup. +In your own code, create those objects as above or select them from an +existing server. `monkeypatch` in the environment example is a pytest fixture. + ### Open a tmux session First, start a tmux session to connect to: diff --git a/docs/api/testing/pytest-plugin/fixtures.md b/docs/api/testing/pytest-plugin/fixtures.md index 3d116427d4..eac1891b08 100644 --- a/docs/api/testing/pytest-plugin/fixtures.md +++ b/docs/api/testing/pytest-plugin/fixtures.md @@ -5,8 +5,9 @@ ## Quick Start Add a fixture name as a test parameter — [pytest] creates and injects it -automatically. You never call fixtures yourself. In doctests, libtmux injects -the same objects through `doctest_namespace`: +automatically. You never call fixtures yourself. This repository's +`conftest.py` also injects objects through `doctest_namespace` for the examples +below; downstream doctests need their own namespace setup: ```python >>> created_session = server.new_session(session_name="my-session") diff --git a/docs/api/testing/pytest-plugin/usage.md b/docs/api/testing/pytest-plugin/usage.md index 9ae0fd49d8..c57a827762 100644 --- a/docs/api/testing/pytest-plugin/usage.md +++ b/docs/api/testing/pytest-plugin/usage.md @@ -37,14 +37,15 @@ object lookups in the test grid. [pytest-tmux] also works through {ref}`pytest fixtures `, so the same fixture concepts apply. -The plugin's fixtures guarantee a fresh, headless {command}`tmux(1)` server, session, window, or pane is -passed into your test. +Request `server` or `session` as a test parameter to receive a fresh, headless +{command}`tmux(1)` server or session. Derive windows and panes from the session; +the plugin does not register `window` or `pane` fixtures. (recommended-fixtures)= ## Recommended fixtures -These fixtures are automatically used when the plugin is enabled and `pytest` is run. +Requesting `server` or `session` also creates their filesystem dependencies: - Creating temporary, test directories for: - `/home/` ({fixture}`home_path`) @@ -53,7 +54,8 @@ These fixtures are automatically used when the plugin is enabled and `pytest` is - `base-index -g 1` - These are set to ensure panes and windows can be reliably referenced and asserted. + To make tmux read this configuration, set the temporary home directory as + shown in {ref}`set_home`, or pass `config_file` to a server you create. (setting_a_tmux_configuration)= diff --git a/docs/topics/context_managers.md b/docs/topics/context_managers.md index d42b16b8b0..4763c95933 100644 --- a/docs/topics/context_managers.md +++ b/docs/topics/context_managers.md @@ -37,6 +37,16 @@ Import `libtmux`: ## Server context manager +Exiting a {class}`~libtmux.Server` context calls `kill-server`, which kills +every session on that socket. Use a socket you own for disposable work rather +than the default server holding your interactive sessions. + +Here `Server` is this repository's isolated {fixture}`TestServer` factory; +each call gets a unique socket and registered cleanup. In application code, +use `libtmux.Server(socket_path=...)` with a socket inside a +`tempfile.TemporaryDirectory`. Nest the server context inside the directory +context so the server stops before its socket is removed. + You create a temporary server that will be killed when you're done: ```python From a410e103170014e4ec48cb3f892028abb66725a0 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Wed, 28 Oct 2026 19:51:39 -0500 Subject: [PATCH 3/5] docs(fix[automation]): Match printed markers why: Echoed command text could falsely signal completed work. what: - Match complete marker lines on fresh output lines - Use monotonic bounds and predicate waits in examples - Distinguish progress, completion and process exit status --- docs/topics/automation_patterns.md | 121 ++++++++++++++++------------- docs/topics/pane_interaction.md | 27 ++++--- 2 files changed, 84 insertions(+), 64 deletions(-) diff --git a/docs/topics/automation_patterns.md b/docs/topics/automation_patterns.md index 8d4811c2a7..06c9010a5a 100644 --- a/docs/topics/automation_patterns.md +++ b/docs/topics/automation_patterns.md @@ -47,6 +47,11 @@ doctests against a live tmux server; in your own scripts you create them yoursel {meth}`~libtmux.Server.new_session`). Each example builds its own window or pane and tears it down at the end, so the snippets stand alone and don't depend on each other. +Completion checks match complete output lines so the echoed command cannot +satisfy its own marker. Use a fresh pane or markers unique to each invocation; +an old marker in scrollback is not evidence about a new command. These checks +observe output, not the command's exit status. + ## Process control ### Starting long-running processes @@ -78,30 +83,36 @@ going in the pane. The pane object stays your handle on that running work. Because {meth}`send_keys() ` doesn't wait, you find out whether a command is still running the same way a person would: by reading what's on screen. Capture the pane and look for a marker your command prints -when it reaches a known state. +when it reaches a known state. A progress marker remains in scrollback after +the process finishes; its presence alone does not show that it is still running. ```python >>> import time ->>> status_window = session.new_window(window_name='status-check', attach=False) +>>> from libtmux.test.retry import retry_until +>>> status_window = session.new_window( +... window_name='status-check', attach=False, window_shell='sh' +... ) >>> status_pane = status_window.active_pane ->>> def is_process_running(pane, marker='RUNNING'): -... """Check if a marker indicates process is still running.""" +>>> def has_output_marker(pane, marker='RUNNING'): +... """Check whether a complete marker line has appeared.""" ... output = pane.capture_pane() -... return marker in '\\n'.join(output) +... return marker in [line.strip() for line in output] >>> # Start and mark a process ->>> status_pane.send_keys('echo "RUNNING"; sleep 0.3; echo "DONE"') ->>> time.sleep(0.1) +>>> status_pane.send_keys('echo; echo "RUNNING"; sleep 0.3; echo "DONE"') +>>> retry_until(lambda: has_output_marker(status_pane), seconds=1) +True ->>> # Check while running ->>> 'RUNNING' in '\\n'.join(status_pane.capture_pane()) +>>> # Check that progress was reported +>>> has_output_marker(status_pane) True >>> # Wait for completion ->>> time.sleep(0.5) ->>> 'DONE' in '\\n'.join(status_pane.capture_pane()) +>>> retry_until(lambda: has_output_marker(status_pane, 'DONE'), seconds=1) +True +>>> has_output_marker(status_pane, 'DONE') True >>> # Clean up @@ -126,15 +137,15 @@ the round-trips. >>> def wait_for_output(pane, text, timeout=5.0, poll_interval=0.1): ... """Wait for specific text to appear in pane output.""" -... start = time.time() -... while time.time() - start < timeout: -... output = '\\n'.join(pane.capture_pane()) +... start = time.monotonic() +... while time.monotonic() - start < timeout: +... output = [line.strip() for line in pane.capture_pane()] ... if text in output: ... return True ... time.sleep(poll_interval) ... return False ->>> monitor_pane.send_keys('sleep 0.2; echo "READY"') +>>> monitor_pane.send_keys('echo; sleep 0.2; echo "READY"') >>> wait_for_output(monitor_pane, 'READY', timeout=2.0) True @@ -241,11 +252,13 @@ Window(@... ...) >>> for pane, cmd in tasks: ... pane.send_keys(cmd) ->>> # Wait for all tasks ->>> time.sleep(0.5) - ->>> # Verify all completed ->>> all('DONE' in '\\n'.join(p.capture_pane()) for p, _ in tasks) +>>> from libtmux.test.retry import retry_until +>>> def all_done(): +... return all( +... f'DONE{i}' in [line.strip() for line in p.capture_pane()] +... for i, (p, _) in enumerate(tasks, 1) +... ) +>>> retry_until(all_done, seconds=2) True >>> # Clean up @@ -273,18 +286,18 @@ Window(@... ...) >>> def wait_all_complete(panes, marker='COMPLETE', timeout=10.0): ... """Wait for all panes to show completion marker.""" -... start = time.time() +... start = time.monotonic() ... remaining = set(range(len(panes))) -... while remaining and time.time() - start < timeout: +... while remaining and time.monotonic() - start < timeout: ... for i in list(remaining): -... if marker in '\\n'.join(panes[i].capture_pane()): +... if marker in (line.strip() for line in panes[i].capture_pane()): ... remaining.remove(i) ... time.sleep(0.1) ... return len(remaining) == 0 >>> # Start tasks with different durations >>> for i, pane in enumerate(panes): -... pane.send_keys(f'sleep 0.{i+1}; echo "COMPLETE"') +... pane.send_keys(f'echo; sleep 0.{i+1}; echo "COMPLETE"') >>> # Wait for all >>> wait_all_complete(panes, 'COMPLETE', timeout=2.0) @@ -302,7 +315,7 @@ Cleanup is the part of automation that's easy to forget — and forgetting leave orphaned sessions and windows behind on the tmux server. A `with` block makes the cleanup automatic: the session lives for the body and is killed on the way out, even if an exception interrupts you. It costs a little to spin a session up and tear it -down, but you get a guaranteed-clean slate that never leaks. +down, but the context manager runs cleanup on both success and failure. ```python >>> # Create isolated session for a task @@ -361,12 +374,12 @@ catch instead of a script that quietly stalls. >>> def run_with_timeout(pane, command, marker='__DONE__', timeout=5.0): ... """Run command and wait for completion with timeout.""" -... pane.send_keys(f'{command}; echo {marker}') -... start = time.time() -... while time.time() - start < timeout: -... output = '\\n'.join(pane.capture_pane()) -... if marker in output: -... return output +... pane.send_keys(f'{command}; echo; echo {marker}') +... start = time.monotonic() +... while time.monotonic() - start < timeout: +... lines = pane.capture_pane() +... if marker in (line.strip() for line in lines): +... return '\n'.join(lines) ... time.sleep(0.1) ... raise CommandTimeout(f'Command timed out after {timeout}s') @@ -389,7 +402,9 @@ up for. Tune both for how expensive the command is and how patient you can be. ```python >>> import time ->>> retry_window = session.new_window(window_name='retry-demo', attach=False) +>>> retry_window = session.new_window( +... window_name='retry-demo', attach=False, window_shell='sh' +... ) >>> retry_pane = retry_window.active_pane >>> def retry_until_success(pane, command, success_marker, max_retries=3, delay=0.5): @@ -397,14 +412,13 @@ up for. Tune both for how expensive the command is and how patient you can be. ... for attempt in range(max_retries): ... pane.send_keys(command) ... time.sleep(delay) -... output = '\\n'.join(pane.capture_pane()) -... if success_marker in output: +... if success_marker in (line.strip() for line in pane.capture_pane()): ... return True, attempt + 1 ... return False, max_retries >>> # Test retry >>> success, attempts = retry_until_success( -... retry_pane, 'echo "OK"', 'OK', max_retries=3, delay=0.2 +... retry_pane, 'echo; echo "OK"', 'OK', max_retries=3, delay=0.2 ... ) >>> success True @@ -425,7 +439,7 @@ genuinely a pipeline of steps, not a single call. A task queue runs a list of commands in order, waiting for each to finish before starting the next. You tag every task with an indexed marker so you know exactly -which step you're waiting on, and you collect a pass/fail result per task. +which step you're waiting on, and you collect a completion/timeout result per task. ```python >>> import time @@ -437,12 +451,13 @@ which step you're waiting on, and you collect a pass/fail result per task. ... """Process a queue of tasks sequentially.""" ... results = [] ... for i, task in enumerate(tasks): -... pane.send_keys(f'{task}; echo "{completion_marker}_{i}"') +... pane.send_keys(f'{task}; echo; echo "{completion_marker}_{i}"') ... # Wait for this task to complete -... start = time.time() -... while time.time() - start < 5.0: -... output = '\\n'.join(pane.capture_pane()) -... if f'{completion_marker}_{i}' in output: +... start = time.monotonic() +... while time.monotonic() - start < 5.0: +... if f'{completion_marker}_{i}' in ( +... line.strip() for line in pane.capture_pane() +... ): ... results.append((i, True)) ... break ... time.sleep(0.1) @@ -481,10 +496,9 @@ and the history tells you how far you got before it stopped. ... state_name, command, next_marker = states[current_state] ... pane.send_keys(command) ... -... start = time.time() -... while time.time() - start < timeout_per_state: -... output = '\\n'.join(pane.capture_pane()) -... if next_marker in output: +... start = time.monotonic() +... while time.monotonic() - start < timeout_per_state: +... if next_marker in (line.strip() for line in pane.capture_pane()): ... history.append(state_name) ... current_state += 1 ... break @@ -495,9 +509,9 @@ and the history tells you how far you got before it stopped. ... return history, True >>> states = [ -... ('init', 'echo "INIT_DONE"', 'INIT_DONE'), -... ('process', 'echo "PROCESS_DONE"', 'PROCESS_DONE'), -... ('cleanup', 'echo "CLEANUP_DONE"', 'CLEANUP_DONE'), +... ('init', 'echo; echo "INIT_DONE"', 'INIT_DONE'), +... ('process', 'echo; echo "PROCESS_DONE"', 'PROCESS_DONE'), +... ('cleanup', 'echo; echo "CLEANUP_DONE"', 'CLEANUP_DONE'), ... ] >>> history, success = run_state_machine(state_pane, states) @@ -523,12 +537,15 @@ then reacts to what actually happened rather than to a clock. >>> bp_pane = bp_window.active_pane >>> # Good: Use completion marker ->>> bp_pane.send_keys('long_command; echo "__DONE__"') +>>> bp_pane.send_keys('true; echo; echo "__DONE__"') >>> # Then poll for marker ->>> import time ->>> time.sleep(0.2) ->>> '__DONE__' in '\\n'.join(bp_pane.capture_pane()) +>>> from libtmux.test.retry import retry_until +>>> complete = retry_until( +... lambda: '__DONE__' in [line.strip() for line in bp_pane.capture_pane()], +... seconds=2, +... ) +>>> complete True >>> bp_window.kill() diff --git a/docs/topics/pane_interaction.md b/docs/topics/pane_interaction.md index 6bde824ec6..eed197650e 100644 --- a/docs/topics/pane_interaction.md +++ b/docs/topics/pane_interaction.md @@ -278,21 +278,25 @@ interval until a marker you control appears. It's a busy wait, not an event, but it's reliable across shells and commands because you're checking the one thing that matters: what actually printed. +Match complete output lines so the echoed command cannot satisfy its own +marker. Use a fresh pane or a marker unique to the invocation, since old +markers remain in scrollback. A completion marker does not report exit status. + ### Polling for completion marker ```python >>> import time ->>> pane.send_keys('sleep 0.2; echo "TASK_COMPLETE"') +>>> pane.send_keys('echo; sleep 0.2; echo "TASK_COMPLETE"') >>> # Poll for completion >>> for _ in range(30): ... output = pane.capture_pane() -... if 'TASK_COMPLETE' in '\\n'.join(output): +... if 'TASK_COMPLETE' in [line.strip() for line in output]: ... break ... time.sleep(0.1) ->>> 'TASK_COMPLETE' in '\\n'.join(output) +>>> 'TASK_COMPLETE' in [line.strip() for line in output] True ``` @@ -306,15 +310,15 @@ logic: >>> def wait_for_text(pane, text, timeout=5.0): ... """Wait for text to appear in pane output.""" -... start = time.time() -... while time.time() - start < timeout: +... start = time.monotonic() +... while time.monotonic() - start < timeout: ... output = pane.capture_pane() -... if text in '\\n'.join(output): +... if text in [line.strip() for line in output]: ... return True ... time.sleep(0.1) ... return False ->>> pane.send_keys('echo "READY"') +>>> pane.send_keys('echo; echo "READY"') >>> wait_for_text(pane, 'READY', timeout=2.0) True ``` @@ -492,12 +496,11 @@ you've already met. >>> def run_and_capture(pane, command, marker='__DONE__', timeout=5.0): ... """Run a command and return its output.""" -... pane.send_keys(f'{command}; echo {marker}') -... start = time.time() -... while time.time() - start < timeout: +... pane.send_keys(f'{command}; echo; echo {marker}') +... start = time.monotonic() +... while time.monotonic() - start < timeout: ... output = pane.capture_pane() -... output_str = '\\n'.join(output) -... if marker in output_str: +... if marker in [line.strip() for line in output]: ... return output # Return all captured output ... time.sleep(0.1) ... raise TimeoutError(f'Command did not complete within {timeout}s') From b2977d087a5c55532c9956f518acbe914c57dd81 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Wed, 28 Oct 2026 19:51:39 -0500 Subject: [PATCH 4/5] docs(fix[tmux]): State command semantics why: Examples need to match tmux defaults and shell boundaries. what: - Document explicit split attachment and the default prefix - Qualify history suppression and literal key handling --- docs/glossary.md | 2 +- docs/quickstart.md | 16 ++++++++-------- docs/topics/pane_interaction.md | 5 ++++- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/docs/glossary.md b/docs/glossary.md index 08fe112186..467635b7df 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -38,7 +38,7 @@ Session The session has 1 or more {term}`Window`. The bottom bar in tmux show a list of windows. Normally they can be navigated with - ``Ctrl-a [0-9]``, ``Ctrl-a n`` and ``Ctrl-a p``. + ``Ctrl-b [0-9]``, ``Ctrl-b n`` and ``Ctrl-b p`` with tmux's default prefix. Sessions can have a ``session_name``. diff --git a/docs/quickstart.md b/docs/quickstart.md index 8e879ebd3b..b1f6971d80 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -384,14 +384,14 @@ window. You have two ways you can move your cursor to new sessions, windows and panes. -For one, arguments such as `attach=False` can be omittted. +Pass `attach=True` to select the new pane. The default is `attach=False`. ```python ->>> pane = window.split() +>>> pane = window.split(attach=True) ``` This gives you the {class}`~libtmux.Pane` along with moving the cursor to a new -window. You can also use the `.select_*` available on the object; in this case +pane. You can also use the `.select_*` available on the object; in this case the pane has {meth}`Pane.select() `. ```python @@ -437,17 +437,17 @@ Pane(%1 ...) ### Avoid cluttering shell history -`suppress_history=True` can send commands to pane windows and sessions **without** -them being visible in the history. +`suppress_history=True` prefixes the command with a space. The shell must be +configured to ignore commands with leading spaces for this to suppress history. ```python >>> pane.send_keys('echo Howdy', enter=True, suppress_history=True) ``` In this case, {meth}`Pane.send_keys() ` has -" `echo Howdy`" written, 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 +" `echo Howdy`" written and sent with a leading space. libtmux does not change +the shell's history settings. The default `enter=True` sends the command without +needing to use {meth}`pane.enter() ` after. ## Working with options diff --git a/docs/topics/pane_interaction.md b/docs/topics/pane_interaction.md index eed197650e..868ab3f8d4 100644 --- a/docs/topics/pane_interaction.md +++ b/docs/topics/pane_interaction.md @@ -87,7 +87,10 @@ True Both tmux and your shell interpret certain characters. Pass `literal=True` to send them through untouched, so a tab or escape arrives as the literal byte -rather than a key tmux acts on: +rather than a key tmux acts on. + +Literal mode bypasses tmux's key-name parsing; the shell still interprets +the resulting text. ```python >>> import time From b62fca1feef2ece8f3c2cef8d122789a886606bb Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Wed, 28 Oct 2026 19:51:39 -0500 Subject: [PATCH 5/5] docs(fix[policy]): Align project reference why: Duplicated guides drifted from the project's actual contracts. what: - Link the authoritative release and changelog policies - Describe configured CI targets and current deprecations - Correct the API landing-page link --- docs/project/compatibility.md | 7 ++--- docs/project/deprecations.md | 11 +++++++- docs/project/public-api.md | 2 +- docs/project/releasing.md | 50 +++++------------------------------ 4 files changed, 22 insertions(+), 48 deletions(-) diff --git a/docs/project/compatibility.md b/docs/project/compatibility.md index 4dc5347e14..e1942a2b6a 100644 --- a/docs/project/compatibility.md +++ b/docs/project/compatibility.md @@ -3,13 +3,13 @@ ## Python - **Minimum**: Python 3.10 -- **Tested**: Python 3.10, 3.11, 3.12, 3.13 +- **CI targets**: see the [CI matrix](https://github.com/tmux-python/libtmux/blob/master/.github/workflows/tests.yml) - **Maximum**: Python < 4.0 ## tmux - **Minimum**: tmux 3.2a -- **Tested**: latest stable tmux release +- **CI targets**: the releases and development branch in the matrix above - libtmux uses tmux's format system and control mode -- older tmux versions may lack required format variables @@ -24,6 +24,7 @@ ## Known Limitations -- tmux must be running and accessible via the default socket or a specified socket +- Commands targeting existing objects need a running, accessible tmux server. + {meth}`~libtmux.Server.new_session` starts a server when needed. - Some operations require the tmux server to have at least one session - Format string availability depends on tmux version diff --git a/docs/project/deprecations.md b/docs/project/deprecations.md index 631102b8cc..10340d47d9 100644 --- a/docs/project/deprecations.md +++ b/docs/project/deprecations.md @@ -6,7 +6,16 @@ Active deprecations with timeline and migration paths. -No active deprecations at this time. +The following {class}`~libtmux.Window` methods emit `DeprecationWarning`: + +| Deprecated | Replacement | +|------------|-------------| +| {meth}`~libtmux.Window.set_window_option` | {meth}`~libtmux.Window.set_option` | +| {meth}`~libtmux.Window.show_window_option` | {meth}`~libtmux.Window.show_option` | +| {meth}`~libtmux.Window.show_window_options` | {meth}`~libtmux.Window.show_options` | + +The legacy `g` parameter on option methods also emits `DeprecationWarning`; +use `global_` instead. No removal release is scheduled here. See [history](../history.md) for past changes and the [migration guide](../migration.md) for upgrading between versions. diff --git a/docs/project/public-api.md b/docs/project/public-api.md index ba7eae89a4..61aae8836d 100644 --- a/docs/project/public-api.md +++ b/docs/project/public-api.md @@ -2,7 +2,7 @@ ## What Is Public -Every module documented under [API Reference](index.md) is public API. +Every module documented under [API Reference](../api/index.md) is public API. This includes: ### Core Library diff --git a/docs/project/releasing.md b/docs/project/releasing.md index 46e3d38903..9d67130ed6 100644 --- a/docs/project/releasing.md +++ b/docs/project/releasing.md @@ -7,51 +7,15 @@ 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 +Follow the [contributor release checklist] for version updates, commits, and +publication to [PyPI]. The owner creates and pushes release tags; tags trigger +the publishing workflow. ## 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) -``` +Follow [the changelog policy] for the `CHANGES` structure, unreleased entries, +and release-time lead paragraphs. +[contributor release checklist]: https://github.com/tmux-python/libtmux/blob/master/.github/CONTRIBUTING.md#releasing +[the changelog policy]: https://github.com/tmux-python/libtmux/blob/master/.github/WRITING.md#the-changelog [PyPI]: https://pypi.org/project/libtmux/