From 1801a69c924dddb72847b9df31b4efe14feb8aca Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Wed, 30 Sep 2026 17:25:01 -0500 Subject: [PATCH] docs(guides) Make shared guides runnable in tmux why: Shared guides mixed language fragments that depended on missing imports, setup and state from other examples. Port readers need their own API semantics and complete programs they can copy and execute. what: - Teach five general guides with complete tmux shell programs - Add owned guide pages for the eight previously shared languages - Route library examples to verified capture and attach programs - Link external APIs and regular Server objects used after test fixtures - Extend the native runner and receipts to cover shell guides - Preserve guide anchors and enforce the 100-column comment limit verification: Run all five displayed programs on tmux 3.2a and 3.7c. Exercise empty and ambiguous matches, assertion failures, input timeout and cleanup failure. Reject changed example bytes before execution. Check copied code and port navigation at desktop and phone widths. The final outer loop passes in 45.6 seconds. --- WRITING.md | 2 + scripts/check-example-prose.py | 19 +- .../content/docs/guides/capturing-output.md | 244 +++---- .../content/docs/guides/getting-started.md | 239 ++---- site/src/content/docs/guides/index.md | 13 +- .../docs/guides/querying-and-filtering.md | 217 +++--- site/src/content/docs/guides/sending-keys.md | 176 +++-- .../docs/guides/testing-with-libtmux.md | 241 +++--- .../docs/ports/cxx/guides/capturing-output.md | 48 ++ .../docs/ports/cxx/guides/getting-started.md | 44 ++ .../cxx/guides/querying-and-filtering.md | 47 ++ .../docs/ports/cxx/guides/sending-keys.md | 44 ++ .../ports/cxx/guides/testing-with-libtmux.md | 44 ++ .../ports/dotnet/guides/capturing-output.md | 48 ++ .../ports/dotnet/guides/getting-started.md | 44 ++ .../dotnet/guides/querying-and-filtering.md | 47 ++ .../docs/ports/dotnet/guides/sending-keys.md | 43 ++ .../dotnet/guides/testing-with-libtmux.md | 40 + .../docs/ports/go/guides/capturing-output.md | 48 ++ .../docs/ports/go/guides/getting-started.md | 44 ++ .../ports/go/guides/querying-and-filtering.md | 47 ++ .../docs/ports/go/guides/sending-keys.md | 44 ++ .../ports/go/guides/testing-with-libtmux.md | 41 ++ .../ports/java/guides/capturing-output.md | 48 ++ .../docs/ports/java/guides/getting-started.md | 44 ++ .../java/guides/querying-and-filtering.md | 47 ++ .../docs/ports/java/guides/sending-keys.md | 44 ++ .../ports/java/guides/testing-with-libtmux.md | 50 ++ .../docs/ports/py/guides/capturing-output.md | 47 ++ .../docs/ports/py/guides/getting-started.md | 43 ++ .../ports/py/guides/querying-and-filtering.md | 46 ++ .../docs/ports/py/guides/sending-keys.md | 42 ++ .../ports/py/guides/testing-with-libtmux.md | 40 + .../docs/ports/rs/guides/capturing-output.md | 48 ++ .../docs/ports/rs/guides/getting-started.md | 44 ++ .../ports/rs/guides/querying-and-filtering.md | 47 ++ .../docs/ports/rs/guides/sending-keys.md | 43 ++ .../ports/rs/guides/testing-with-libtmux.md | 41 ++ .../ports/swift/guides/capturing-output.md | 48 ++ .../ports/swift/guides/getting-started.md | 44 ++ .../swift/guides/querying-and-filtering.md | 47 ++ .../docs/ports/swift/guides/sending-keys.md | 43 ++ .../swift/guides/testing-with-libtmux.md | 41 ++ .../docs/ports/ts/guides/capturing-output.md | 48 ++ .../docs/ports/ts/guides/getting-started.md | 44 ++ .../ports/ts/guides/querying-and-filtering.md | 47 ++ .../docs/ports/ts/guides/sending-keys.md | 43 ++ .../ports/ts/guides/testing-with-libtmux.md | 40 + site/src/data/mentions.json | 684 +++++++++++++++--- site/test/complete-examples.test.ts | 27 +- site/test/fixtures/guide-examples.json | 110 +++ 51 files changed, 2935 insertions(+), 829 deletions(-) create mode 100644 site/src/content/docs/ports/cxx/guides/capturing-output.md create mode 100644 site/src/content/docs/ports/cxx/guides/getting-started.md create mode 100644 site/src/content/docs/ports/cxx/guides/querying-and-filtering.md create mode 100644 site/src/content/docs/ports/cxx/guides/sending-keys.md create mode 100644 site/src/content/docs/ports/cxx/guides/testing-with-libtmux.md create mode 100644 site/src/content/docs/ports/dotnet/guides/capturing-output.md create mode 100644 site/src/content/docs/ports/dotnet/guides/getting-started.md create mode 100644 site/src/content/docs/ports/dotnet/guides/querying-and-filtering.md create mode 100644 site/src/content/docs/ports/dotnet/guides/sending-keys.md create mode 100644 site/src/content/docs/ports/dotnet/guides/testing-with-libtmux.md create mode 100644 site/src/content/docs/ports/go/guides/capturing-output.md create mode 100644 site/src/content/docs/ports/go/guides/getting-started.md create mode 100644 site/src/content/docs/ports/go/guides/querying-and-filtering.md create mode 100644 site/src/content/docs/ports/go/guides/sending-keys.md create mode 100644 site/src/content/docs/ports/go/guides/testing-with-libtmux.md create mode 100644 site/src/content/docs/ports/java/guides/capturing-output.md create mode 100644 site/src/content/docs/ports/java/guides/getting-started.md create mode 100644 site/src/content/docs/ports/java/guides/querying-and-filtering.md create mode 100644 site/src/content/docs/ports/java/guides/sending-keys.md create mode 100644 site/src/content/docs/ports/java/guides/testing-with-libtmux.md create mode 100644 site/src/content/docs/ports/py/guides/capturing-output.md create mode 100644 site/src/content/docs/ports/py/guides/getting-started.md create mode 100644 site/src/content/docs/ports/py/guides/querying-and-filtering.md create mode 100644 site/src/content/docs/ports/py/guides/sending-keys.md create mode 100644 site/src/content/docs/ports/py/guides/testing-with-libtmux.md create mode 100644 site/src/content/docs/ports/rs/guides/capturing-output.md create mode 100644 site/src/content/docs/ports/rs/guides/getting-started.md create mode 100644 site/src/content/docs/ports/rs/guides/querying-and-filtering.md create mode 100644 site/src/content/docs/ports/rs/guides/sending-keys.md create mode 100644 site/src/content/docs/ports/rs/guides/testing-with-libtmux.md create mode 100644 site/src/content/docs/ports/swift/guides/capturing-output.md create mode 100644 site/src/content/docs/ports/swift/guides/getting-started.md create mode 100644 site/src/content/docs/ports/swift/guides/querying-and-filtering.md create mode 100644 site/src/content/docs/ports/swift/guides/sending-keys.md create mode 100644 site/src/content/docs/ports/swift/guides/testing-with-libtmux.md create mode 100644 site/src/content/docs/ports/ts/guides/capturing-output.md create mode 100644 site/src/content/docs/ports/ts/guides/getting-started.md create mode 100644 site/src/content/docs/ports/ts/guides/querying-and-filtering.md create mode 100644 site/src/content/docs/ports/ts/guides/sending-keys.md create mode 100644 site/src/content/docs/ports/ts/guides/testing-with-libtmux.md create mode 100644 site/test/fixtures/guide-examples.json diff --git a/WRITING.md b/WRITING.md index ddc1a3d1..78aaf190 100644 --- a/WRITING.md +++ b/WRITING.md @@ -124,6 +124,8 @@ filtering page, including its displayed setup and error assertions. Use `--example concept --port fsharp --page concepts/queries` for the native concept pages. Each run command checks its own expected output; a later success cannot hide an earlier missing result. +Use `--example guide --page guides/capturing-output` for a root guide's shell +program. Select the tmux release through `PATH`; the result records `tmux -V`. Put explanatory comments on separate lines above the code they describe. Limit example comments to 100 columns, including indentation; prefer shorter diff --git a/scripts/check-example-prose.py b/scripts/check-example-prose.py index 5f7257cc..4e8cd57d 100644 --- a/scripts/check-example-prose.py +++ b/scripts/check-example-prose.py @@ -2,6 +2,7 @@ """Run a complete example exactly as displayed, including its setup. Use --port to choose a language and --output-dir for a new evidence directory. +Use --example guide --page guides/ for a shared tmux shell program. The selected language's native tools, Git, and tmux must already be on PATH. This downloads and builds dependencies; it belongs outside routine site tests. """ @@ -19,15 +20,18 @@ def main(): repo = Path(__file__).resolve().parent.parent parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--example', choices=['capture', 'attach', 'query', 'concept'], default='capture') - parser.add_argument('--port', required=True) - parser.add_argument('--page', help='Page path within the port, for example concepts/queries') + parser.add_argument('--example', choices=['capture', 'attach', 'query', 'concept', 'guide'], default='capture') + parser.add_argument('--port') + parser.add_argument('--page', help='Guide path, or page within the selected port') parser.add_argument('--output-dir', required=True, type=Path) args = parser.parse_args() manifest = json.loads((repo / f'site/test/fixtures/{args.example}-examples.json').read_text()) - examples = [item for item in manifest['examples'] if item['port'] == args.port] + if args.example != 'guide' and not args.port: + parser.error('--port is required for language examples') + examples = [item for item in manifest['examples'] if args.example == 'guide' or item['port'] == args.port] if args.page: - examples = [item for item in examples if item['page'] == f'ports/{args.port}/{args.page}'] + page_path = args.page if args.example == 'guide' else f'ports/{args.port}/{args.page}' + examples = [item for item in examples if item['page'] == page_path] if len(examples) != 1: choices = ', '.join(item['page'] for item in examples) parser.error(f'Choose one example with --port and --page; matching pages: {choices or "none"}') @@ -70,7 +74,7 @@ def main(): for index, command in enumerate(commands): start = time.monotonic() log = output / f'run-{index + 1}.log' - print(f'Running {args.port}; log: {log}', flush=True) + print(f'Running {example["page"]}; log: {log}', flush=True) with log.open('w') as stream: result = subprocess.run(['sh', '-eu', '-c', command], cwd=output, env=env, stdout=stream, stderr=subprocess.STDOUT) @@ -81,7 +85,8 @@ def main(): break passed = len(results) == len(commands) and all( row['exit'] == 0 and not row['missingOutput'] for row in results) - report = {'port': args.port, 'page': example['page'], 'sourceRevision': example['sourceRevision'], + report = {'port': example['port'], 'page': example['page'], 'sourceRevision': example['sourceRevision'], + 'tmuxVersion': subprocess.check_output(['tmux', '-V'], text=True).strip(), 'pageSha256': hashlib.sha256(page.read_bytes()).hexdigest(), 'files': example['files'], 'runs': results, 'passed': passed, 'scope': 'Exact displayed program and setup; native execution on this host.'} diff --git a/site/src/content/docs/guides/capturing-output.md b/site/src/content/docs/guides/capturing-output.md index f209bfc6..217e824b 100644 --- a/site/src/content/docs/guides/capturing-output.md +++ b/site/src/content/docs/guides/capturing-output.md @@ -1,7 +1,7 @@ --- -supportedPorts: [py, ts, rs, go, java, dotnet, cxx, swift] +supportedPorts: [] title: Capturing output -description: Read a pane's screen or scrollback and wait for output or a completion signal. +description: Capture a tmux pane screen or include its scrollback history. sidebar: label: Capturing output group: Guides @@ -9,183 +9,117 @@ sidebar: tableOfContents: true --- -Capture a pane to read its visible screen or scrollback. After [Sending -keys](../sending-keys/), wait for the expected output or a completion signal -before reading the result. +`capture-pane -p` prints a pane's visible screen. Add `-S -` to start at the oldest +line still present in its scrollback history. Capture is a snapshot of terminal +state; it is not a log of every byte the application wrote. ## Visible pane vs. scrollback -`tmux capture-pane` distinguishes the currently visible screen from the -scrollback history above it. Choose the range required by your task: - -```python -# 0 is the first visible line; positive numbers stay in the visible pane; -# negative numbers reach into history; "-" means "the start of the -# history." With no arguments you get the visible screen. ->>> pane = window.split(shell='sh') ->>> pane.capture_pane() -['$'] -``` - -```typescript -// start counts back from the visible top, so -100 asks for the last -// hundred lines or as many as exist. -const lines = await pane.capture({ start: -100 }); -``` - -```go -// Include scrollback from its beginning through the bottom of the screen. -lines, err := pane.Capture(ctx, tmux.CapturePaneRequest{ - Start: tmux.CaptureBoundary, End: tmux.CaptureBoundary, -}) -if err != nil { - return err -} -for _, line := range lines { - fmt.Println(line) +This example forces output into scrollback: it prints 40 numbered lines in a +pane with 10 rows. A normal capture shows the last screenful. Adding `-S -` +also retrieves earlier lines, including `row-1`. + +Save the script as `history.sh`. It needs tmux 3.2a or newer, a POSIX shell and +fractional `sleep` support. + +```sh title="history.sh" +#!/bin/sh +set -eu +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-guide.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! tmux -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || status=$? + exit "$status" } +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +tmux -S "$socket" -f /dev/null new-session -d -s capture -x 80 -y 10 \ + 'i=1; while [ "$i" -le 40 ]; do printf "row-%s\n" "$i"; i=$((i + 1)); done; exec cat' +tmux -S "$socket" resize-window -t capture:0 -x 80 -y 10 + +attempt=0 +while [ "$attempt" -lt 100 ]; do + screen=$(tmux -S "$socket" capture-pane -p -t capture:0.0) + if printf '%s\n' "$screen" | grep -Fqx 'row-40'; then + printf 'Visible screen:\n%s\n' "$screen" + printf '\nScreen and scrollback:\n' + tmux -S "$socket" capture-pane -p -S - -t capture:0.0 + exit 0 + fi + attempt=$((attempt + 1)) + sleep 0.05 +done +printf '%s\n' 'Timed out waiting for pane output.' >&2 +exit 1 ``` -```rust -// capture() for the visible screen; capture_with(...) for scrollback and -// other options. -let visible = pane.capture().await?; -``` - -```cpp -// A capture that doesn't fit is reported, not silently truncated: -// output_limit says how much you're prepared to hold. -const auto visible = pane.capture(); -const auto history = pane.capture({.whole_history = true}); -``` +Run the saved script: -```swift -// The streaming form (below) additionally tracks a cursor, so a caller can -// ask for only what's new since the last read. -let lines = try await server.capture(pane) +```console +$ sh history.sh ``` - -`pane.capture()` returns visible pane contents as a list of lines. - - -`pane.CaptureAsync()` returns visible pane contents as a list of lines. - +`row-1` appears in the history capture but has already scrolled off the visible +screen. `row-40` appears in both. The script cleans up its private server after +printing or after any failure. -[Capture pane output](/examples/capture-pane-output/) includes complete -programs and source details. +A numeric `-S` chooses a starting row: `0` is the top visible row and negative +values reach into history. `-E` selects the final row. `-J` joins wrapped rows; +`-e` includes terminal escape sequences for attributes such as color. History +is bounded by `history-limit`, so discarded lines cannot be recovered by capture. ## Wait for the expected text -An immediate capture can race the shell, as [Sending -keys](../sending-keys/#the-race-you-cant-see-from-the-call-site) explains. Wait -for the expected text with a timeout so your program stops promptly when the -output arrives and reports a failure if it never does: +Wait for an observable result with a deadline. An immediate capture after +[Sending keys](../sending-keys/) can race the application. Match a complete +output line, as [Capture pane output](/examples/capture-pane-output/) does, to +avoid treating an echoed command as completed work. -```go -// A wait that times out fails with the screen the pane last held rather -// than sending you back to add a print statement. -tmuxtest.WaitForText(ctx, t, pane, "ready") -``` - -```rust -// Looks before it sleeps (text already present is an answer, not a wait) -// and joins wrapped lines so a needle spanning a wrap still matches. The -// result is checked rather than discarded: a deadline reached is still an -// answer you have to look at, not a silent pass. -match pane.wait_for_text("ready", Duration::from_secs(10)).await? { - PaneWait::Arrived => {} - PaneWait::Dead => { /* the pane's process ended before it showed up */ } - PaneWait::TimedOut => { /* still alive, but the deadline ran out first */ } -} -``` - -```typescript -// No fixed-poll helper: subscribe to the event stream *before* sending, -// then wait for the specific event, so a marker printed between the two -// calls is never missed. -const found = live.subscribe().find( - (event) => event.kind === "output" && event.paneId === pane.id && event.data.includes(marker), - { timeoutMs: 30_000 }, -); -await pane.sendKeys(command); -await found; -``` - -```java -// A client has to attach first: attaching is what makes tmux push -// %output at all; a client that never attaches hears command replies and -// nothing else. -EventSubscription output = client.subscribeOutput(32); -``` - -```csharp -// Polls a read function against a predicate rather than sleeping a fixed -// amount. -string output = await TmuxWait.UntilAsync( - async token => string.Join('\n', await pane.CaptureAsync(cancellationToken: token)), - text => text.Contains("hello-from-libtmux", StringComparison.Ordinal), - TimeSpan.FromSeconds(10), - TimeSpan.FromMilliseconds(20)); -``` - -```swift -// Takes patterns for both success and failure, so a process that fails -// fast doesn't have to be discovered by timeout. -try await server.waitForOutput(in: pane, matching: [ready], stoppingAt: [failed]) -``` - - -Use a completion channel when the program can announce that its work is done. -The next section explains that protocol. - - -[Testing with libtmux](../testing-with-libtmux/) explains isolated servers -and fixtures. [Capture pane output](/examples/capture-pane-output/) provides -the full examples. +A screen may change before the next capture. For continuously consumed output, +use a pipe or an attached control-mode client's output events; see +[Control mode vs one-shot](/concepts/transports/). ## Wait for a completion signal -If you control the command, have it signal completion with `tmux wait-for -S -done`. Wait on the same channel to avoid matching screen text: - -```python ->>> server.new_session(session_name='wait_test') -Session(...) ->>> server.wait_for('test_channel', set_flag=True) -``` +A program that controls its own completion can send `wait-for -S` on a dedicated +tmux channel. A matching `wait-for` waits on that server. Use the same socket +and a distinct channel for each task, and put a deadline around the wait. +[Waiting and retrying](/topics/waiting-and-retry/) covers the channel protocol. - -`Server::wait_for(channel, timeout)` also detects a server that dies during -the wait and reports failure. - + -```swift -import LibTmux +## Use a language library -public func waitingOnAChannel(_ server: Server, pane: Pane) async throws { - try await server.run( - "make; \(server.shellInvocation) wait-for -S built", - in: pane - ) - try await server.wait(for: "built") -} -``` +The port dropdown opens that language's capture guide. Complete programs with +imports and setup are available here: -Use a distinct channel name for each task. tmux remembers a signal sent before -a waiter starts; reusing a signalled name can therefore finish an unrelated -later wait. [Waiting and retrying](/topics/waiting-and-retry/) covers channel -APIs and server-loss handling. +[Python](/py/latest/examples/capture-pane-output/) · +[TypeScript](/ts/latest/examples/capture-pane-output/) · +[Go](/go/latest/examples/capture-pane-output/) · +[Rust](/rs/latest/examples/capture-pane-output/) · +[Java](/java/latest/examples/capture-pane-output/) · +[Kotlin](/kotlin/latest/examples/capture-pane-output/) · +[Scala](/scala/latest/examples/capture-pane-output/) · +[.NET](/dotnet/latest/examples/capture-pane-output/) · +[F#](/fsharp/latest/examples/capture-pane-output/) · +[C++](/cxx/latest/examples/capture-pane-output/) · +[Swift](/swift/latest/examples/capture-pane-output/) · +[Ruby](/ruby/latest/examples/capture-pane-output/) · +[Lua](/lua/latest/examples/capture-pane-output/) -## Where to go next +## tmux reference -- [Filtering and querying, in practice](../querying-and-filtering/): once - you're reading more than one pane, finding the right one to capture. -- [Testing with libtmux](../testing-with-libtmux/): the isolated-server - fixtures that make waiting on real tmux practical inside a test suite. -- [Capture pane output](/examples/capture-pane-output/): the full - sourced code for the patterns above. +The [tmux manual](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1) +documents these commands and their flags. diff --git a/site/src/content/docs/guides/getting-started.md b/site/src/content/docs/guides/getting-started.md index 6b80d530..9d906b6f 100644 --- a/site/src/content/docs/guides/getting-started.md +++ b/site/src/content/docs/guides/getting-started.md @@ -1,7 +1,7 @@ --- -supportedPorts: [py, ts, rs, go, java, dotnet, cxx, swift] +supportedPorts: [] title: Getting started -description: Install tmux and the library, then create a session and interact with a pane. +description: Create a tmux session, add a window and split it into panes. sidebar: label: Getting started group: Guides @@ -9,195 +9,96 @@ sidebar: tableOfContents: true --- +Create a session, add a window, and split that window into panes. These are the +same tmux objects that the language libraries control. + ## Install tmux -The common tmux baseline documented here is 3.2a. Individual features can -require a newer release; check your port's compatibility notes. Confirm your -installed version: +Install tmux with your platform's package manager. These examples require tmux +3.2a or newer and a POSIX shell. Confirm the installed version: ```console $ tmux -V ``` -If `tmux` is missing or older than 3.2a, install a supported version with your -platform's package manager. libtmux uses an installed tmux executable. - - -## Pick a port - -Choose the port for your project's language: [Python](/py/), [TypeScript](/ts/), -[Rust](/rs/), [Go](/go/), [Java and Kotlin](/java/), [.NET](/dotnet/), -[C++](/cxx/), or [Swift](/swift/). [Server, session, window, -pane](/concepts/server-session-window-pane/) explains the shared model, and -[Control mode vs one-shot](/concepts/transports/) covers transport differences. - -For a prerelease package, pin an exact version and check its release notes -before upgrading. Check the package version's API reference for supported operations. - - ## Run the smallest thing that proves it works - -Start a tmux session in one terminal: - -```console -$ tmux new-session -s foo -n bar -``` - -Run the example in a second terminal. It uses the session you just created. - - -Install the package using the command shown with its example. -[Attach and send keys](/examples/attach-and-send-keys/) contains the complete -program, prerequisites, and source details. - -```python -# pip install libtmux ->>> import libtmux ->>> server = libtmux.Server() ->>> session = server.sessions[0] ->>> window = session.active_window ->>> pane = window.split(shell='sh') ->>> pane.capture_pane() -['$'] - ->>> pane.send_keys('echo "Hello world"', enter=True) - ->>> pane.capture_pane() -['$ echo "Hello world"', 'Hello world', '$'] -``` - -```typescript -// bun add libtmux -import { Server } from "libtmux"; - -const server = new Server(); -const session = await server.newSession({ name: "work" }); -const editor = await session.newWindow({ name: "editor" }); -await editor.split(); - -await editor.panes.at(0)?.sendKeys("echo hello"); -const lines = await editor.panes.at(0)?.capture(); -``` - -```go -// go get github.com/libtmux/libtmux-go -session, err := server.NewSession(ctx, tmux.NewSessionRequest{ - Name: "libtmux-go-quickstart", WindowName: "start", -}) -if err != nil { - return fmt.Errorf("create session: %w", err) -} - -windowName := "work" -window, err := session.NewWindow(ctx, tmux.NewWindowRequest{Name: &windowName}) -if err != nil { - return fmt.Errorf("create window: %w", err) +Save this complete script as `start.sh`, or paste its entire block into a shell. +It creates a private server, so it does not need an existing session. Each pane +runs `cat` to keep it alive until cleanup. + +```sh title="start.sh" +#!/bin/sh +set -eu +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-guide.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! tmux -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || status=$? + exit "$status" } -pane, err := window.SplitPane(ctx, tmux.SplitPaneRequest{ - Direction: tmux.PaneDirectionRight, -}) -if err != nil { - return fmt.Errorf("split window: %w", err) -} -command := "printf 'libtmux ready\\n'" -if err := pane.SendKeys(ctx, tmux.SendKeysRequest{Command: &command, Literal: true}); err != nil { - return fmt.Errorf("send command: %w", err) -} -``` - -```rust -// cargo add libtmux -use libtmux::Server; - -// TestServer is the isolated, disposable form of this used under `test-support` -// for the port's own tests (see Testing with libtmux): real code just calls -// Server::new() directly, as below. -let server = Server::new()?; -let session = server.new_session("work").await?; -let window = session.new_window("editor").await?; -let pane = window.active_pane().await?.expect("a window has a pane"); - -pane.send_line("echo hello").await?; - -for line in pane.capture().await? { - println!("{}", line.to_string_lossy()); -} -``` - -```java -// implementation("io.github.libtmux:libtmux:VERSION") -Session session = server.newSession("demo"); -Window editor = session.newWindow("editor"); -Pane right = editor.split(); - -session.name(); // → demo -editor.name(); // → editor -editor.refresh().panes().size(); // → 2 +trap cleanup 0 +trap 'exit 1' HUP INT TERM -Pane pane = server.sessions().get(0).windows().get(0).panes().get(0); - -pane.sendLine("echo hello from libtmux"); - -pane.capture().isEmpty(); // → false +tmux -S "$socket" -f /dev/null new-session -d -s work -n main 'cat' +tmux -S "$socket" new-window -t work: -n editor 'cat' +tmux -S "$socket" split-window -h -t work:editor 'cat' +tmux -S "$socket" list-panes -a -F '#{session_name}:#{window_name}' ``` -```csharp -// dotnet add package LibTmux -using LibTmux; - -Server server = await Server.ConnectAsync(); -Session session = await server.CreateSessionAsync(new NewSessionRequest(name: "build")); -Window window = await session.CreateWindowAsync(new NewWindowRequest(name: "tests")); -Pane pane = (await window.GetPanesAsync())[0]; +Run the saved script: -await pane.SendTextAsync("dotnet test"); +```console +$ sh start.sh ``` -```cpp -// vcpkg install libtmux-cxx -const auto sessions = server.sessions(); -// sessions->at(0) is this example's session, from an already-open scratch server. -const libtmux::Session& session = sessions->at(0); +The output contains `work:main` once and `work:editor` twice: one session, two +windows, and three panes. The script stops its server on exit, including after +a failed command. If shutdown fails, it reports and retains the socket path. -// Build an arrangement without composing a single tmux argument. -const auto editor = session.new_window({.name = "editor"}); -const auto logs = editor->split({.horizontal = true, .percentage = 30}); +## What just happened -(void)logs->send_text("journalctl -f"); -(void)logs->send_key("Enter"); +`-S` selects the server socket. `new-session` starts that server and its first +window; `new-window` adds another window; `split-window` adds a pane. `-d` starts +the session without taking over the terminal. `-f /dev/null` starts this private +server without a user configuration file. -// Read a pane's visible contents, or its scrollback. -const auto visible = logs->capture(); -``` +[Server, session, window, pane](/concepts/server-session-window-pane/) +explains the hierarchy. [Attaching to tmux](../attaching-to-tmux/) shows how to +open a session interactively and leave it running after detaching. -```swift -// .package(url: "https://github.com/libtmux/libtmux-swift", from: "0.1.0") -let session = try await server.newSession(named: "work", windowName: "editor") -let logs = try await server.newWindow(in: session, named: "logs").window -let pane = try await server.splitWindow(logs, direction: .right) -try await server.run("tail -f /tmp/build.log", in: pane) +## Pick a port -let lines = try await server.capture(pane) -``` +Use the port dropdown for language-specific installation and APIs. The complete +programs below include imports, project files, setup, error handling and cleanup: + +[Python](/py/latest/examples/capture-pane-output/) · +[TypeScript](/ts/latest/examples/capture-pane-output/) · +[Go](/go/latest/examples/capture-pane-output/) · +[Rust](/rs/latest/examples/capture-pane-output/) · +[Java](/java/latest/examples/capture-pane-output/) · +[Kotlin](/kotlin/latest/examples/capture-pane-output/) · +[Scala](/scala/latest/examples/capture-pane-output/) · +[.NET](/dotnet/latest/examples/capture-pane-output/) · +[F#](/fsharp/latest/examples/capture-pane-output/) · +[C++](/cxx/latest/examples/capture-pane-output/) · +[Swift](/swift/latest/examples/capture-pane-output/) · +[Ruby](/ruby/latest/examples/capture-pane-output/) · +[Lua](/lua/latest/examples/capture-pane-output/) -## What just happened +## Where to go next -A server handle targets tmux without taking over your terminal. Creating a -session starts the server if needed. Sending keys writes input to a pane; the -method's Enter and literal-text options control how tmux interprets it. [Sending -keys](../sending-keys/) explains those defaults. Capture methods read the pane's -screen or a requested scrollback range. +[Sending keys](../sending-keys/) types input, and +[Capturing output](../capturing-output/) reads a result. Use +[Querying and filtering](../querying-and-filtering/) to choose a target. -## Where to go next +## tmux reference -- [Concepts](/concepts/) for the mental model behind what you just did: - the object hierarchy, how commands actually reach tmux, and how filtering - works once you have more than one session to choose from. -- [Attaching to tmux](../attaching-to-tmux/), [Sending keys](../sending-keys/), - and [Capturing output](../capturing-output/) go one level deeper into each - half of the round trip you just ran. -- [Attach and send keys](/examples/attach-and-send-keys/) for the fully - checked version of every block above, and how each one is verified. -- Your port's own API reference (via the port switcher) once you're ready - to look up method details. +The [tmux manual](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1) +documents these commands and their flags. diff --git a/site/src/content/docs/guides/index.md b/site/src/content/docs/guides/index.md index cbfda341..5d0e65cc 100644 --- a/site/src/content/docs/guides/index.md +++ b/site/src/content/docs/guides/index.md @@ -12,8 +12,7 @@ Use these guides to connect to tmux, send input, capture output, query objects, and test your program. [Concepts](/concepts/) explains the object model and transport choices. -- **[Getting started](getting-started/)**: install tmux and the library, then run - an example. +- **[Getting started](getting-started/)**: install tmux and run a complete example. - **[Attaching to tmux](attaching-to-tmux/)**: select a socket and find or create a session. - **[Sending keys](sending-keys/)**: send literal text, named keys, and Enter. @@ -21,8 +20,12 @@ transport choices. wait for a result. - **[Filtering and querying, in practice](querying-and-filtering/)**: apply the lookup contracts from [Filtering and queries](/concepts/queries/). -- **[Testing with libtmux](testing-with-libtmux/)**: use isolated tmux servers +- **[Testing](testing-with-libtmux/)**: use isolated tmux servers and manage test cleanup. -[Examples](/examples/) provides source-backed programs for the same tasks, with -source and validation details on each page. +[Examples](/examples/) provides complete programs with setup and cleanup. + + +The general guides use tmux shell commands. Select a port from the dropdown +for its library APIs, imports and native project setup. + diff --git a/site/src/content/docs/guides/querying-and-filtering.md b/site/src/content/docs/guides/querying-and-filtering.md index a5ac6b0c..bc4093e7 100644 --- a/site/src/content/docs/guides/querying-and-filtering.md +++ b/site/src/content/docs/guides/querying-and-filtering.md @@ -1,7 +1,7 @@ --- -supportedPorts: [py, ts, rs, go, java, dotnet, cxx, swift] -title: Filtering and querying, in practice -description: Filter tmux objects, require one match, and choose where a query runs. +supportedPorts: [] +title: Querying and filtering +description: Find an exact tmux session and select one pane using formats and filters. sidebar: label: Querying and filtering group: Guides @@ -9,149 +9,112 @@ sidebar: tableOfContents: true --- -Find sessions, windows, or panes with collection filters and exactly-one -lookups. [Filtering and queries](/concepts/queries/) explains the result-count -contracts and the choice between local and tmux-side filtering. This guide adds -examples for common queries. +Use an exact target when you know its name, or filter a listing when you need to +inspect several objects. A session named `work` and one named `worker` should +not become interchangeable targets. ## Require exactly one match - -| Port | Collection filter | Exactly-one | Empty | Several | -|------|--------------------|--------------|-------|---------| -| Go | `tmuxq.Where(values, predicate)` | `tmuxq.ExactlyOne(values, predicate)` | `tmuxq.ErrNoMatch` | `tmuxq.ErrMultipleMatches` | -| Rust | `.iter().matching(&expr)` | `.exactly_one()` | prints via the error's `Display` | same, one error type covers both | -| C++ | pipe a range into [`libtmux::matching(expr)`](/cxx/latest/reference/libtmux-matching/) | `libtmux::exactly_one(range)` | `.error()` says which way it went wrong | same call, same error type | - - - - -`tmuxq.ExactlyOne` returns `ErrNoMatch` for no matches and -`ErrMultipleMatches` for an ambiguous result. Keep the error when wrapping it: - -```go -pane, err := tmuxq.ExactlyOne(snapshot.Panes(), func(pane *tmux.Pane) bool { - name, present := pane.CurrentCommand() - return present && name == "nvim" -}) -if err != nil { - return fmt.Errorf("find one editor pane: %w", err) +Prefix a session target with `=` to require an exact name. `has-session` reports +whether it exists; it does not return a pane. This script selects panes in +`work` and rejects both zero matches and multiple matches before using an ID. + +Save the complete script as `query.sh`. It requires tmux 3.2a or newer and a +POSIX shell. It creates and cleans up its own server. + +```sh title="query.sh" +#!/bin/sh +set -eu +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-guide.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! tmux -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || status=$? + exit "$status" } -fmt.Println("editor pane:", pane.ID()) +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +tmux -S "$socket" -f /dev/null new-session -d -s work 'cat' +tmux -S "$socket" new-session -d -s worker 'cat' +tmux -S "$socket" has-session -t '=work' + +panes=$(tmux -S "$socket" list-panes -a \ + -f '#{==:#{session_name},work}' -F '#{pane_id}') +# Pane IDs contain no whitespace; split the rows to count matches. +# shellcheck disable=SC2086 +set -- $panes +if [ "$#" -ne 1 ]; then + printf 'Expected one work pane, found %s.\n' "$#" >&2 + exit 1 +fi +tmux -S "$socket" display-message -p -t "$1" '#{session_name}' ``` - - -Rust's is `examples/find.rs`, run via `cargo run --example find`: +Run the saved script: -```rust -match panes.iter().matching(&running).exactly_one() { - Ok(pane) => println!("exactly one: {}", pane.id()), - Err(error) => println!("not exactly one: {error}"), -} +```console +$ sh query.sh ``` - - -C++'s is quoted straight from `examples/05-readme.cpp`'s `cardinality` -region into `README.md`, and `tools/docs/check_readme.py` fails the build -if the two ever disagree: +The output is `work`. The `worker` session remains outside the result. Targeting +the returned pane ID avoids repeating name matching when the next command runs. +An object can still disappear between commands; keep errors visible. -```cpp -auto addressed = *panes | libtmux::matching(libtmux::pane::id == panes->at(0).id()); -if (const auto one = libtmux::exactly_one(addressed); one.has_value()) { - std::printf("exactly one: %s\n", std::string{one->get().id()}.c_str()); -} -``` - - - -`IEnumerable.Matching(expression)` returns all matching objects. -Choose an exactly-one operation only when an absent or ambiguous target should -stop the task. - - -`hasSession(_:)` checks existence. It does not select a single matching object. -See [Attaching to tmux](../attaching-to-tmux/). - - -[Filtering and queries](/concepts/queries/) describes the exactly-one method -and its missing- or multiple-match errors. - - - ## Declarative filters -A query document can be stored in configuration and evaluated against captured -objects. It does not contain an arbitrary callback. - -```csharp -// Turns a LINQ expression into a portable QueryDocument (or throws), -// evaluated locally over objects you already hold rather than compiled -// into tmux's own format language. Translate(...) produces the document -// directly when you want the wire form without also running the filter. -// Stable wire names map Session.Name to session_name in the query document. -IReadOnlyList building = sessions.Matching( - session => session.Name.StartsWith("build", StringComparison.Ordinal) && session.Attached); -``` +`list-panes -a` searches every session. `-f` evaluates a tmux format as a boolean +for each pane; here `#{==:#{session_name},work}` keeps only exact session-name +matches. `-F` chooses what each returned row contains. Using only `#{pane_id}` +keeps the result easy to pass to another tmux command. -```swift -// Built from key paths, so a text operator on a number is a compile error, -// and it holds no closures, so it encodes for an MCP tool call. -let expression = FilterExpr.where(\.currentCommand, .isIn(["nvim", "vim"])) -``` - - - ## Case-insensitive matching -```python -# An i-prefixed lookup. -session.windows.filter(window_name__istartswith="bg") -``` - -```typescript -// mode: "insensitive" on the comparison, rather than a separate lookup name. -snapshot.sessions.where({ name: { contains: "API", mode: "insensitive" } }); -``` - -```swift -// Passed to the regex pattern itself rather than to the filter. -let editors = try RegexPattern("^(n?vim|hx)$", options: [.caseInsensitive]) -let expression = FilterExpr.where(\.currentCommand, .matches(editors)) -``` - - -Use a predicate with `strings.EqualFold` for case-insensitive equality: - -```go -matches := tmuxq.Where(snapshot.Sessions(), func(session *tmux.Session) bool { - name, present := session.Name() - return present && strings.EqualFold(name, "api") -}) -fmt.Println("matching sessions:", len(matches)) -``` - - - +Choose case handling explicitly when a name may vary in capitalization. tmux's +`m` format operator supports an `i` modifier for case-insensitive matching. +Keep the ordinary `==` comparison when exact case is part of your contract. +[Filtering and queries](/concepts/queries/) explains the query model. ## Push the filter into tmux, or read once and filter locally -Use a tmux-side filter to reduce the rows returned, or query a snapshot when you -need several answers from one read. [Filtering and queries](/concepts/queries/) -explains that choice. Unknown format tokens expand to empty values, so -validate an unexpectedly empty search before concluding that no objects match. - -## Where to go next - -- [Attach and send keys](/examples/attach-and-send-keys/): its - "Finding an existing session instead" section is this guide's recipes - applied to one concrete lookup. -- [Testing with libtmux](../testing-with-libtmux/): most of the fixtures - there hand you a server with exactly one thing on it, which is precisely - when an exactly-one query is the right tool instead of a filter you then - index into. +A tmux-side filter reduces returned rows. Capturing a listing once and filtering +it in your program is useful when several decisions should use the same read. +Neither approach reserves the objects. Unknown format names expand to empty +values; check an unexpectedly empty result before assuming nothing exists. + + + +## Use a language library + +The port dropdown opens the language's query guide. These complete programs +connect to an existing server, find exactly the `work` session and report its +absence: + +[Python](/py/latest/guides/attaching-to-tmux/) · +[TypeScript](/ts/latest/guides/attaching-to-tmux/) · +[Go](/go/latest/guides/attaching-to-tmux/) · +[Rust](/rs/latest/guides/attaching-to-tmux/) · +[Java](/java/latest/guides/attaching-to-tmux/) · +[Kotlin](/kotlin/latest/guides/attaching-to-tmux/) · +[Scala](/scala/latest/guides/attaching-to-tmux/) · +[.NET](/dotnet/latest/guides/attaching-to-tmux/) · +[F#](/fsharp/latest/guides/attaching-to-tmux/) · +[C++](/cxx/latest/guides/attaching-to-tmux/) · +[Swift](/swift/latest/guides/attaching-to-tmux/) · +[Ruby](/ruby/latest/guides/attaching-to-tmux/) · +[Lua](/lua/latest/guides/attaching-to-tmux/) + +## tmux reference + +The [tmux manual](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1) +documents these commands and their flags. diff --git a/site/src/content/docs/guides/sending-keys.md b/site/src/content/docs/guides/sending-keys.md index f525c647..f9a28786 100644 --- a/site/src/content/docs/guides/sending-keys.md +++ b/site/src/content/docs/guides/sending-keys.md @@ -1,7 +1,7 @@ --- -supportedPorts: [py, ts, rs, go, java, dotnet, cxx, swift] +supportedPorts: [] title: Sending keys -description: Literal text versus tmux key names, whether Enter is pressed for you, and why a command can outrun the shell about to run it. +description: Send literal text or named keys to a tmux pane and distinguish input from completion. sidebar: label: Sending keys group: Guides @@ -9,112 +9,102 @@ sidebar: tableOfContents: true --- -Send literal text to type characters into a pane, or send tmux key names such as -`C-c`, `Enter`, and `Up` to press those keys. Check the method's literal-text -and Enter defaults: typing the word `Enter` and pressing Enter are different -operations. +`send-keys -l` types literal characters. Without `-l`, tmux recognizes key names +such as `Enter`, `C-c`, and `Up`. Typing the word `Enter` and pressing Enter are +separate operations. ## Literal text, key names, and whether Enter follows -These examples show each port's text, named-key, and Enter behavior. [Attach and -send keys](/examples/attach-and-send-keys/) provides the full source examples -and validation details. - -```python -# literal=True disables tmux's key-name lookup; left at its default, a -# string that happens to look like a key name is interpreted as one. -pane.send_keys(cmd, literal=True) - -# enter defaults to True. Pass enter=False to type without submitting, then -# press Enter yourself: the README's own example, to show the steps apart. -pane.send_keys('echo hey', enter=False) -pane.enter() +This complete script types the word `Enter` into a pane running `cat`, then +presses the Enter key. Neither command starts an interactive tmux client. +Save it as `send.sh` and run it in a POSIX shell with tmux 3.2a or newer and +fractional `sleep` support. + +```sh title="send.sh" +#!/bin/sh +set -eu +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-guide.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! tmux -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || status=$? + exit "$status" +} +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +tmux -S "$socket" -f /dev/null new-session -d -s input 'cat' +tmux -S "$socket" send-keys -t input:0.0 -l 'Enter' +tmux -S "$socket" send-keys -t input:0.0 Enter + +attempt=0 +while [ "$attempt" -lt 100 ]; do + screen=$(tmux -S "$socket" capture-pane -p -t input:0.0) + if printf '%s\n' "$screen" | grep -Fqx 'Enter'; then + printf '%s\n' "$screen" + exit 0 + fi + attempt=$((attempt + 1)) + sleep 0.05 +done +printf '%s\n' 'Timed out waiting for typed input.' >&2 +exit 1 ``` -```typescript -// literal: true reads the text as characters even when it could be read as -// a tmux key name. sendKeys presses Enter unless you say otherwise. -await pane.sendKeys("q", { enter: false, literal: true }); -``` +Run the saved script: -```go -command := "printf 'ready\\n'" -if err := pane.SendKeys(ctx, tmux.SendKeysRequest{ - Command: &command, Literal: true, SkipEnter: true, -}); err != nil { - return err -} -if err := pane.Enter(ctx); err != nil { - return err -} +```console +$ sh send.sh ``` -```rust -// send_keys always sends with tmux's "-l" (literal) flag: key names such as -// C-c are typed rather than interpreted. It sends no Enter. -pane.send_keys("echo hey").await?; +The screen contains `Enter`: the terminal echoes the input, and `cat` writes it +back after the newline. The polling loop waits for visible text and fails after +100 unsuccessful checks. Cleanup stops only the private server. -// send_line sends literal text *and* Enter as one dispatch, so cancelling -// this future cannot leave a completed text send without its Enter. -pane.send_line("echo hey").await?; +Literal input disables tmux's key-name lookup. The application still interprets +those characters. In a shell pane, that includes shell quoting, expansions and +commands; literal mode does not make shell input safe to compose from arbitrary +text. -// send_key_names takes actual key names, for when you mean the key. -pane.send_key_names(["C-c"]).await?; -``` +## The race you can't see from the call site -```cpp -// Literal text, never interpreted as key names or formats, and never -// followed by a newline the caller did not ask for. -pane.send_text("echo hey"); +Completing `send-keys` means tmux accepted the input. It does not establish that +the application read it or finished a command. Terminal echo can appear before +the application processes a line. -// One named key, sent separately: this is how Enter gets pressed. -pane.send_key("Enter"); -``` +For a shell command, wait for its distinct output or a completion signal before +using the result. [Capture pane output](/examples/capture-pane-output/) matches +a complete output line so the echoed command cannot satisfy the check. +[Capturing output](../capturing-output/) explains screen and history capture. -```csharp -await pane.SendTextAsync("echo hey", cancellationToken: ct); -await pane.EnterAsync(ct); -``` + -```java -// Sends the text and submits it as one call: no separate literal switch -// and no documented "type without submitting" step as of this page. -pane.sendLine("echo hey"); -``` +## Use a language library -```swift -// One call: types the command line, then presses Enter. No literal switch -// and no separate "type, don't submit" step is exposed at this level. -try await server.run("echo hey", in: pane) -``` +Each port's complete capture program sends a command, waits for its output and +cleans up. Use the port dropdown for its input APIs, or open the program: -Literal input disables tmux's key-name lookup. The program inside the pane -still interprets that input, including shell quoting and expansions. -[Concepts](/concepts/) introduces the shared tmux model. +[Python](/py/latest/examples/capture-pane-output/) · +[TypeScript](/ts/latest/examples/capture-pane-output/) · +[Go](/go/latest/examples/capture-pane-output/) · +[Rust](/rs/latest/examples/capture-pane-output/) · +[Java](/java/latest/examples/capture-pane-output/) · +[Kotlin](/kotlin/latest/examples/capture-pane-output/) · +[Scala](/scala/latest/examples/capture-pane-output/) · +[.NET](/dotnet/latest/examples/capture-pane-output/) · +[F#](/fsharp/latest/examples/capture-pane-output/) · +[C++](/cxx/latest/examples/capture-pane-output/) · +[Swift](/swift/latest/examples/capture-pane-output/) · +[Ruby](/ruby/latest/examples/capture-pane-output/) · +[Lua](/lua/latest/examples/capture-pane-output/) -## The race you can't see from the call site +## tmux reference -Completing `send-keys` means tmux accepted the input. The shell may still be -starting, and the command may still be running. Use a wait that checks the state your next operation requires. - - -Use a bounded `retry_until` loop when shell startup can discard early input. - - -`tmuxtest.WaitForShellReady` waits for a ready shell in tests. It does not wait -for a submitted command to finish. - - -Use an output predicate with `TmuxWait.UntilAsync` to wait for a command result. - - -Wait for shell readiness before sending input when startup matters. Then wait -for the command's expected output or a completion signal before reading its -result. The next guide covers those waiting APIs. - -## Where to go next - -- [Capturing output](../capturing-output/): reading back what you just - sent, and waiting for it correctly instead of guessing a delay. -- [Attach and send keys](/examples/attach-and-send-keys/): the full - sourced round trip this guide picks apart piece by piece. +The [tmux manual](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1) +documents these commands and their flags. diff --git a/site/src/content/docs/guides/testing-with-libtmux.md b/site/src/content/docs/guides/testing-with-libtmux.md index 56ea6148..802101fd 100644 --- a/site/src/content/docs/guides/testing-with-libtmux.md +++ b/site/src/content/docs/guides/testing-with-libtmux.md @@ -1,183 +1,104 @@ --- -supportedPorts: [py, ts, rs, go, java, dotnet, cxx, swift] -title: Testing with libtmux -description: Test against a private tmux server and clean up its sessions and socket. +supportedPorts: [] +title: Testing with tmux +description: Test on a private tmux server and preserve errors during cleanup. sidebar: - label: Testing with libtmux + label: Testing with tmux group: Guides order: 7 tableOfContents: true --- -Use a private tmux server to test code that creates sessions, sends input, or -captures output. The fixtures below allocate a separate socket and manage normal -test cleanup. Give each test suite its own socket so it cannot target a -developer's existing sessions. - - -Request the fixture to obtain its server. Python's `session` fixture depends on -`server`, so requesting a session also creates an isolated server: - -```python ->>> def test_example(session: "Session") -> None: -... assert isinstance(session.name, str) -... assert session.name.startswith('libtmux_') -... window = session.new_window(window_name='new one') -... assert window.name == 'new one' -``` - -That exact block is a doctest in `src/libtmux/pytest_plugin.py`, checked by -running it as a nested pytest run and asserting it passes. `session_params` -overrides how the fixture builds a session (window size, for instance) -without forking it; a temporary `HOME` and tmux config keep window and pane -indices stable across machines, so an assertion like `window_name == "test"` -doesn't depend on whatever `.tmux.conf` the test runner happens to have. - - - -```go -package example_test - -import ( - "context" - "os" - "testing" - "time" - - "github.com/libtmux/libtmux-go/tmux/tmuxtest" -) - -func TestMain(m *testing.M) { - os.Exit(tmuxtest.Main(m)) -} - -func TestProgram(t *testing.T) { - ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) - defer cancel() - pane := tmuxtest.RunInPane(ctx, t, "printf 'ready\\n'; cat") - tmuxtest.WaitForLine(ctx, t, pane, "ready") +Give each test its own tmux socket. Start it with a known configuration, assert +the state your program needs, and stop only the server the test owns. This keeps +a test run separate from your interactive sessions. + +## Run an isolated test + +This complete shell test creates a session and a window, checks their names, +and prints `tmux fixture passed` on success. It requires tmux 3.2a or newer and +a POSIX shell. Save it as `test-tmux.sh`. + +```sh title="test-tmux.sh" +#!/bin/sh +set -eu +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-guide.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! tmux -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || status=$? + exit "$status" } +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +tmux -S "$socket" -f /dev/null new-session -d -s fixture -n main 'cat' +tmux -S "$socket" new-window -t fixture: -n worker 'cat' +name=$(tmux -S "$socket" display-message -p -t fixture:worker '#{session_name}') +if [ "$name" != fixture ]; then + printf 'Expected fixture, got %s.\n' "$name" >&2 + exit 1 +fi +windows=$(tmux -S "$socket" list-windows -t '=fixture' -F '#{window_name}') +if ! printf '%s\n' "$windows" | grep -Fqx worker; then + printf '%s\n' 'The worker window was not created.' >&2 + exit 1 +fi +printf '%s\n' 'tmux fixture passed' ``` -`tmuxtest.NewServer(ctx, t)` captures the environment and working directory, -resolves the tmux executable, and creates a server on its own socket. -Setup failures stop the test. Test cleanup kills the server, -and wait failures include the last captured screen. Call `tmuxtest.Main` once -from `TestMain` before using these helpers. Run the test with `go test`; tmux -3.2a or newer must be available on `PATH`. - - - -```rust -let guard = TestServer::new().await?; -let server = guard.server(); -// ... drive `server` normally ... -guard.shutdown().await?; -``` +Run the saved script: -Enable the `test-support` feature in a dev-dependency. The crate README uses -these guards in doctests through `#![doc = include_str!("../README.md")]`. -`libtmux::test::retry_until(deadline, condition)` polls an arbitrary async -condition; `Pane::wait_for_text` waits specifically for pane text. See -[Capturing output](../capturing-output/). - - - -```java -@ExtendWith(TmuxExtension.class) -class MyToolTest { - @Test - void itRunsSomethingInAPane(Server server) { - Session session = server.sessions().get(0); - Pane pane = session.windows().get(0).panes().get(0); - pane.sendLine("echo hello"); - assertTrue(pane.capture().stream().anyMatch(line -> line.contains("hello"))); - } -} +```console +$ sh test-tmux.sh ``` -`libtmux-junit5` supplies each test with a running `Server` containing a session -named `libtmux`. Request `TmuxSocketPath` when your code takes a socket path. -Fixtures live in JUnit's per-test extension store. A shutdown hook kills servers -owned by that JVM, and startup cleanup removes servers left by JVMs that have -exited. Source: `libtmux-junit5/README.md`. +The trap preserves a failed assertion's exit status and also reports cleanup +failures. If the server cannot be stopped, its socket directory stays available +for inspection. Each invocation gets a fresh directory from `mktemp`. -The port's `docs` module compiles Java fences -from READMEs and guides, then runs them against `libtmux-junit5` servers. A -`` directive can instead require a named exception, a -compile failure, or an explicit skip reason. Source: `docs/README.md`. - +## Wait for the state you assert - -```csharp -using LibTmux.Testing; +A successful `send-keys` call establishes that tmux accepted input, not that the +application finished processing it. For output assertions, use a bounded wait +such as the [complete capture example](/examples/capture-pane-output/). A fixed +pause alone cannot establish that the result arrived. -TmuxTestFactory factory = new(); -await using TemporaryHierarchyScope scope = await factory.CreateHierarchyAsync(); + -await scope.Pane.SendTextAsync("echo hello"); -``` +## Use a language test fixture -`LibTmux.Testing` ships as a separate package, under -`src/LibTmux.Testing/`. `await using` disposes the scope and kills its server -when the block exits. Use `TmuxWait.UntilAsync` to wait for expected state; see -[Capturing output](../capturing-output/). Source: `README.md`, "Testing your own -code," exercised by `ReadmeExampleTests`. - - - -```cpp -// A private tmux for a suite of your own, gone when the scope ends. -auto fixture = libtmux::test::ScopedTmuxServer::start( - {.socket_namespace = libtmux::test::SocketNamespace::consumer("my-suite")}); -if (!fixture.has_value()) { - std::fprintf(stderr, "%s\n", fixture.error().c_str()); - return 1; -} -const auto under_test = - libtmux::Server::at_socket_path(fixture->socket_path().string()); -std::printf("sessions on it: %zu\n", under_test->sessions()->size()); -``` +Language ports have different fixture and lifetime APIs. Select a port from +the dropdown for its testing guidance. The complete programs below demonstrate +creating a private server, checking a result and cleaning up through that port: -The example comes from `README.md`, "Testing your own tmux tools," and is -checked against the `fixture` region in `examples/05-readme.cpp` by -`tools/docs/check_readme.py`. Enable the `testing` CMake component with -`find_package(libtmux COMPONENTS testing)`. It creates a private socket and -temporary directory, sets `TMUX_TMPDIR`, and removes `TMUX` and `TMUX_PANE` from -the child environment. `SocketNamespace::consumer(...)` labels sockets with the -consumer suite's name. `examples/tests/README.md` shows use from outside the -library's build tree. - - - -```swift -import Testing -import TmuxFixture - -try await withTmuxServer { server in - let sessions = try await server.sessions() - #expect(sessions.map(\.name) == ["bootstrap"]) -} -``` +[Python](/py/latest/examples/capture-pane-output/) · +[TypeScript](/ts/latest/examples/capture-pane-output/) · +[Go](/go/latest/examples/capture-pane-output/) · +[Rust](/rs/latest/examples/capture-pane-output/) · +[Java](/java/latest/examples/capture-pane-output/) · +[Kotlin](/kotlin/latest/examples/capture-pane-output/) · +[Scala](/scala/latest/examples/capture-pane-output/) · +[.NET](/dotnet/latest/examples/capture-pane-output/) · +[F#](/fsharp/latest/examples/capture-pane-output/) · +[C++](/cxx/latest/examples/capture-pane-output/) · +[Swift](/swift/latest/examples/capture-pane-output/) · +[Ruby](/ruby/latest/examples/capture-pane-output/) · +[Lua](/lua/latest/examples/capture-pane-output/) -`TmuxFixture` is a separate package product. It starts a server with a bootstrap -session and limits concurrent fixtures to reduce process and pseudo-terminal -exhaustion. `LIBTMUX_TMUX_BIN` selects the executable; otherwise it checks -installed locations. Source: `Tests/TmuxFixture/README.md`. - +## Where to go next - -TypeScript's harness at `packages/libtmux/src/_internal/test/testkit.ts` is -internal and unpublished. For external tests, create an isolated `Server` and -manage its cleanup in your test framework. - +[Attaching to tmux](../attaching-to-tmux/) connects to a server that should remain +running. [Querying and filtering](../querying-and-filtering/) selects an exact +target, and [Capturing output](../capturing-output/) reads its screen. -## Where to go next +## tmux reference -- [Capturing output](../capturing-output/): the wait helpers most of these - fixtures are meant to be used alongside, instead of a fixed `sleep` in a - test. -- [Attach and send keys](/examples/attach-and-send-keys/) and - [Capture pane output](/examples/capture-pane-output/): the same - operations these fixtures give you a server to run, shown as tested - examples in their own right. +The [tmux manual](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1) +documents these commands and their flags. diff --git a/site/src/content/docs/ports/cxx/guides/capturing-output.md b/site/src/content/docs/ports/cxx/guides/capturing-output.md new file mode 100644 index 00000000..910658d9 --- /dev/null +++ b/site/src/content/docs/ports/cxx/guides/capturing-output.md @@ -0,0 +1,48 @@ +--- +port: cxx +route: guides/capturing-output +title: Capturing output +description: Read pane output with an explicit completion condition. +sidebar: + label: Capturing output + group: Guides + order: 5 +tableOfContents: true +--- + +`Pane::capture` returns captured text through a result that must be checked. +The complete program splits that text into lines and compares a whole line +before its deadline. Capture options select history and bound the output size; +an exceeded output limit is reported as an error. + +## Run the complete program + +[Capture pane output](../../examples/capture-pane-output/) includes the full +C++ program, all imports, project files and a run command. The program +matches `libtmux capture ready` as a complete line, so the echoed command cannot +satisfy the check. It creates and cleans up its own tmux server. + +## Visible pane vs. scrollback + +A capture reads terminal state. Lines that have scrolled beyond retained history +are unavailable, and repeated captures can miss intermediate output. Choose the +range your task needs and use a stream or completion signal when every output +event matters. + + + +## Wait for the expected text + +Use a deadline and a specific output predicate. The example's short polling +pause limits work between checks; it is the observed line that determines +completion. [Sending keys](../sending-keys/) explains why returning from the +input call is not a completion signal. + + + +## Wait for a completion signal + +A program can also signal a dedicated tmux channel. Use the same server endpoint +for the sender and waiter and a new channel name per task. +[Waiting and retrying](/topics/waiting-and-retry/) documents this port's waiting +APIs and failure handling. diff --git a/site/src/content/docs/ports/cxx/guides/getting-started.md b/site/src/content/docs/ports/cxx/guides/getting-started.md new file mode 100644 index 00000000..88e2445a --- /dev/null +++ b/site/src/content/docs/ports/cxx/guides/getting-started.md @@ -0,0 +1,44 @@ +--- +port: cxx +route: guides/getting-started +title: Getting started +description: Run a complete program with this language library. +sidebar: + label: Getting started + group: Guides + order: 2 +tableOfContents: true +--- + +Use `libtmux` to create sessions, send input and read pane output from C++. +The [complete capture program](../../examples/capture-pane-output/) includes +imports, its entry point, project files, dependency setup and a run command. + +## Run the smallest thing that proves it works + +Open that example in an empty directory and save the files using their displayed +names. Its setup pins the library revision that was used to execute the program. +It needs tmux on `PATH` and the native tools named on the example page. + +The program creates a private server, sends a command, waits for the complete +line `libtmux capture ready`, then cleans up. A timeout or command failure is +reported. It does not need an existing tmux session. + +## What just happened + +`Pane::capture` returns captured text through a result that must be checked. +The complete program splits that text into lines and compares a whole line +before its deadline. Capture options select history and bound the output size; +an exceeded output limit is reported as an error. + +## Connect to an existing server + +Use the [complete attach program](../attaching-to-tmux/) to select an existing +socket and find the `work` session. That example leaves tmux running; its +launcher owns setup and cleanup for trying it safely. + +## Where to go next + +[Sending keys](../sending-keys/) explains input, and +[Capturing output](../capturing-output/) explains completion. +[Querying and filtering](../querying-and-filtering/) selects a target. diff --git a/site/src/content/docs/ports/cxx/guides/querying-and-filtering.md b/site/src/content/docs/ports/cxx/guides/querying-and-filtering.md new file mode 100644 index 00000000..f26bedc7 --- /dev/null +++ b/site/src/content/docs/ports/cxx/guides/querying-and-filtering.md @@ -0,0 +1,47 @@ +--- +port: cxx +route: guides/querying-and-filtering +title: Querying and filtering +description: Choose a target and handle missing or ambiguous results. +sidebar: + label: Querying and filtering + group: Guides + order: 6 +tableOfContents: true +--- + +Check the results from `Server::at_socket_path` and `Server::sessions` before +reading either value. The complete program compares `Session::name` with +`"work"`, reports an absent session and keeps the existing server running. +`libtmux::exactly_one` can enforce a one-element result for a filtered range. + + + +## Require exactly one match + +[Attaching to tmux](../attaching-to-tmux/) provides the complete C++ program +and its setup. It searches an existing server for `work`, prints the name and +reports an absent session. Its launcher checks that the server remains running. + +For a more general predicate, decide whether zero or several results are valid +before indexing a collection. Keep lookup and command failures visible: another +client can change the server between a read and the operation using its result. + + + +## Declarative filters + +[Filtering and queries](/concepts/queries/) describes this port's query APIs, +accepted fields and result-count contracts. Use that contract when storing a +query in configuration. + +## Case-insensitive matching + +Choose case handling explicitly when your query needs it. The attach program +uses exact case because the intended session is named `work`. + +## Push the filter into tmux, or read once and filter locally + +A tmux-side filter reduces returned rows; a captured collection can answer +several local queries from one read. Neither reserves the result. Check +unexpectedly empty format values before assuming that an object does not exist. diff --git a/site/src/content/docs/ports/cxx/guides/sending-keys.md b/site/src/content/docs/ports/cxx/guides/sending-keys.md new file mode 100644 index 00000000..2517c196 --- /dev/null +++ b/site/src/content/docs/ports/cxx/guides/sending-keys.md @@ -0,0 +1,44 @@ +--- +port: cxx +route: guides/sending-keys +title: Sending keys +description: Send text and named keys, then wait for the result. +sidebar: + label: Sending keys + group: Guides + order: 4 +tableOfContents: true +--- + +`Pane::send_text` sends literal characters without an added newline. +`Pane::send_key` sends a named key such as Enter; `Pane::send_line` sends a +complete line. Check each result before proceeding. A failed operation carries +its diagnostic in the error value. + +## Run the complete program + +[Capture pane output](../../examples/capture-pane-output/) supplies the full +C++ program, imports, project setup and run command. It starts a private +server, sends a command, checks a complete output line and cleans up. + +## Literal text, key names, and whether Enter follows + +Choose the method and options for the input you mean to send. Literal text +still goes to an application: a shell interprets its quoting, expansions and +commands. Key-name handling and shell interpretation are separate concerns. + +## The race you can't see from the call site + +A successful send means tmux accepted input. The pane's program may still be +starting or processing that input. Wait for the state the next operation needs. +Terminal echo alone does not prove command completion. + +`Pane::capture` returns captured text through a result that must be checked. +The complete program splits that text into lines and compares a whole line +before its deadline. Capture options select history and bound the output size; +an exceeded output limit is reported as an error. + +## Where to go next + +[Capturing output](../capturing-output/) covers the output side. +[Attaching to tmux](../attaching-to-tmux/) connects to a server you already own. diff --git a/site/src/content/docs/ports/cxx/guides/testing-with-libtmux.md b/site/src/content/docs/ports/cxx/guides/testing-with-libtmux.md new file mode 100644 index 00000000..b709184e --- /dev/null +++ b/site/src/content/docs/ports/cxx/guides/testing-with-libtmux.md @@ -0,0 +1,44 @@ +--- +port: cxx +route: guides/testing-with-libtmux +title: Testing with libtmux +description: Use an isolated server and check cleanup failures. +sidebar: + label: Testing with libtmux + group: Guides + order: 7 +tableOfContents: true +--- + +The complete capture program uses `libtmux::test::ScopedTmuxServer` to own a +private server and report teardown failures. It includes the public testing +header and links the testing library explicitly in its CMake setup. + +This fixture is temporary example scaffolding. In an application, use the +regular [`Server`](../../reference/libtmux-server/) connected to the server you +manage. Future versions of that example will use the regular server object +directly. + +## Run a complete example + +[Capture pane output](../../examples/capture-pane-output/) includes a complete +C++ executable, imports, dependency setup and cleanup. Its output check fails +when the expected line does not arrive before the deadline. Start with that +program when adapting the pattern to your own test runner. + +## Keep server ownership explicit + +Give each test a private socket and a known tmux configuration. Stop the server +the test creates, including when startup or an assertion fails. Preserve the +original failure and report cleanup errors so a leaked server remains visible. + +A connection to an existing server has a different lifetime. The +[attach program](../attaching-to-tmux/) leaves that server running and lets its +launcher own cleanup. + +## Wait for the state you assert + +Wait for the actual output or completion condition with a deadline. +[Sending keys](../sending-keys/) returning successfully does not establish +that the application finished. [Capturing output](../capturing-output/) explains +the difference between a screen snapshot and a stream of output. diff --git a/site/src/content/docs/ports/dotnet/guides/capturing-output.md b/site/src/content/docs/ports/dotnet/guides/capturing-output.md new file mode 100644 index 00000000..78eb8618 --- /dev/null +++ b/site/src/content/docs/ports/dotnet/guides/capturing-output.md @@ -0,0 +1,48 @@ +--- +port: dotnet +route: guides/capturing-output +title: Capturing output +description: Read pane output with an explicit completion condition. +sidebar: + label: Capturing output + group: Guides + order: 5 +tableOfContents: true +--- + +`Pane.CaptureAsync` reads visible pane lines. The complete program passes a +cancellation token, compares whole lines and bounds its waits with a +[`CancellationTokenSource`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource?view=net-10.0). Its `OwnedServerScope` owns the private server and +is disposed with `await using`. + +## Run the complete program + +[Capture pane output](../../examples/capture-pane-output/) includes the full +.NET program, all imports, project files and a run command. The program +matches `libtmux capture ready` as a complete line, so the echoed command cannot +satisfy the check. It creates and cleans up its own tmux server. + +## Visible pane vs. scrollback + +A capture reads terminal state. Lines that have scrolled beyond retained history +are unavailable, and repeated captures can miss intermediate output. Choose the +range your task needs and use a stream or completion signal when every output +event matters. + + + +## Wait for the expected text + +Use a deadline and a specific output predicate. The example's short polling +pause limits work between checks; it is the observed line that determines +completion. [Sending keys](../sending-keys/) explains why returning from the +input call is not a completion signal. + + + +## Wait for a completion signal + +A program can also signal a dedicated tmux channel. Use the same server endpoint +for the sender and waiter and a new channel name per task. +[Waiting and retrying](/topics/waiting-and-retry/) documents this port's waiting +APIs and failure handling. diff --git a/site/src/content/docs/ports/dotnet/guides/getting-started.md b/site/src/content/docs/ports/dotnet/guides/getting-started.md new file mode 100644 index 00000000..f837f198 --- /dev/null +++ b/site/src/content/docs/ports/dotnet/guides/getting-started.md @@ -0,0 +1,44 @@ +--- +port: dotnet +route: guides/getting-started +title: Getting started +description: Run a complete program with this language library. +sidebar: + label: Getting started + group: Guides + order: 2 +tableOfContents: true +--- + +Use `LibTmux` to create sessions, send input and read pane output from .NET. +The [complete capture program](../../examples/capture-pane-output/) includes +imports, its entry point, project files, dependency setup and a run command. + +## Run the smallest thing that proves it works + +Open that example in an empty directory and save the files using their displayed +names. Its setup pins the library revision that was used to execute the program. +It needs tmux on `PATH` and the native tools named on the example page. + +The program creates a private server, sends a command, waits for the complete +line `libtmux capture ready`, then cleans up. A timeout or command failure is +reported. It does not need an existing tmux session. + +## What just happened + +`Pane.CaptureAsync` reads visible pane lines. The complete program passes a +cancellation token, compares whole lines and bounds its waits with a +[`CancellationTokenSource`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource?view=net-10.0). Its `OwnedServerScope` owns the private server and +is disposed with `await using`. + +## Connect to an existing server + +Use the [complete attach program](../attaching-to-tmux/) to select an existing +socket and find the `work` session. That example leaves tmux running; its +launcher owns setup and cleanup for trying it safely. + +## Where to go next + +[Sending keys](../sending-keys/) explains input, and +[Capturing output](../capturing-output/) explains completion. +[Querying and filtering](../querying-and-filtering/) selects a target. diff --git a/site/src/content/docs/ports/dotnet/guides/querying-and-filtering.md b/site/src/content/docs/ports/dotnet/guides/querying-and-filtering.md new file mode 100644 index 00000000..03bec27e --- /dev/null +++ b/site/src/content/docs/ports/dotnet/guides/querying-and-filtering.md @@ -0,0 +1,47 @@ +--- +port: dotnet +route: guides/querying-and-filtering +title: Querying and filtering +description: Choose a target and handle missing or ambiguous results. +sidebar: + label: Querying and filtering + group: Guides + order: 6 +tableOfContents: true +--- + +`Server.HasSessionAsync` checks whether a session exists. It does not return +a selected session object. The complete program checks `work`, reports its +absence and leaves the borrowed server running. Use collection filtering when +you need handles or must detect several matching objects. + + + +## Require exactly one match + +[Attaching to tmux](../attaching-to-tmux/) provides the complete .NET program +and its setup. It searches an existing server for `work`, prints the name and +reports an absent session. Its launcher checks that the server remains running. + +For a more general predicate, decide whether zero or several results are valid +before indexing a collection. Keep lookup and command failures visible: another +client can change the server between a read and the operation using its result. + + + +## Declarative filters + +[Filtering and queries](/concepts/queries/) describes this port's query APIs, +accepted fields and result-count contracts. Use that contract when storing a +query in configuration. + +## Case-insensitive matching + +Choose case handling explicitly when your query needs it. The attach program +uses exact case because the intended session is named `work`. + +## Push the filter into tmux, or read once and filter locally + +A tmux-side filter reduces returned rows; a captured collection can answer +several local queries from one read. Neither reserves the result. Check +unexpectedly empty format values before assuming that an object does not exist. diff --git a/site/src/content/docs/ports/dotnet/guides/sending-keys.md b/site/src/content/docs/ports/dotnet/guides/sending-keys.md new file mode 100644 index 00000000..e89c4af2 --- /dev/null +++ b/site/src/content/docs/ports/dotnet/guides/sending-keys.md @@ -0,0 +1,43 @@ +--- +port: dotnet +route: guides/sending-keys +title: Sending keys +description: Send text and named keys, then wait for the result. +sidebar: + label: Sending keys + group: Guides + order: 4 +tableOfContents: true +--- + +`Pane.SendTextAsync` sends text; `Pane.EnterAsync` submits the line. Pass a +cancellation token to both operations. The complete program performs these +steps separately and waits for its output before treating the task as done. + +## Run the complete program + +[Capture pane output](../../examples/capture-pane-output/) supplies the full +.NET program, imports, project setup and run command. It starts a private +server, sends a command, checks a complete output line and cleans up. + +## Literal text, key names, and whether Enter follows + +Choose the method and options for the input you mean to send. Literal text +still goes to an application: a shell interprets its quoting, expansions and +commands. Key-name handling and shell interpretation are separate concerns. + +## The race you can't see from the call site + +A successful send means tmux accepted input. The pane's program may still be +starting or processing that input. Wait for the state the next operation needs. +Terminal echo alone does not prove command completion. + +`Pane.CaptureAsync` reads visible pane lines. The complete program passes a +cancellation token, compares whole lines and bounds its waits with a +[`CancellationTokenSource`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource?view=net-10.0). Its `OwnedServerScope` owns the private server and +is disposed with `await using`. + +## Where to go next + +[Capturing output](../capturing-output/) covers the output side. +[Attaching to tmux](../attaching-to-tmux/) connects to a server you already own. diff --git a/site/src/content/docs/ports/dotnet/guides/testing-with-libtmux.md b/site/src/content/docs/ports/dotnet/guides/testing-with-libtmux.md new file mode 100644 index 00000000..755fcac4 --- /dev/null +++ b/site/src/content/docs/ports/dotnet/guides/testing-with-libtmux.md @@ -0,0 +1,40 @@ +--- +port: dotnet +route: guides/testing-with-libtmux +title: Testing with libtmux +description: Use an isolated server and check cleanup failures. +sidebar: + label: Testing with libtmux + group: Guides + order: 7 +tableOfContents: true +--- + +`LibTmux.Testing` is a separate package. `TmuxTestFactory` creates a temporary +hierarchy whose scope is disposed with `await using`. Use a bounded predicate +wait for state assertions. The complete capture program shows ownership with +`Server.CreateOwnedAsync` and a cancellation token in a standalone executable. + +## Run a complete example + +[Capture pane output](../../examples/capture-pane-output/) includes a complete +.NET executable, imports, dependency setup and cleanup. Its output check fails +when the expected line does not arrive before the deadline. Start with that +program when adapting the pattern to your own test runner. + +## Keep server ownership explicit + +Give each test a private socket and a known tmux configuration. Stop the server +the test creates, including when startup or an assertion fails. Preserve the +original failure and report cleanup errors so a leaked server remains visible. + +A connection to an existing server has a different lifetime. The +[attach program](../attaching-to-tmux/) leaves that server running and lets its +launcher own cleanup. + +## Wait for the state you assert + +Wait for the actual output or completion condition with a deadline. +[Sending keys](../sending-keys/) returning successfully does not establish +that the application finished. [Capturing output](../capturing-output/) explains +the difference between a screen snapshot and a stream of output. diff --git a/site/src/content/docs/ports/go/guides/capturing-output.md b/site/src/content/docs/ports/go/guides/capturing-output.md new file mode 100644 index 00000000..fd3fe24c --- /dev/null +++ b/site/src/content/docs/ports/go/guides/capturing-output.md @@ -0,0 +1,48 @@ +--- +port: go +route: guides/capturing-output +title: Capturing output +description: Read pane output with an explicit completion condition. +sidebar: + label: Capturing output + group: Guides + order: 5 +tableOfContents: true +--- + +`Pane.Capture` takes a `tmux.CapturePaneRequest`. Use `Start` and `End` to choose +a range; `tmux.CaptureBoundary` reaches the corresponding history or screen +boundary. The complete program reads the visible screen, compares full lines +and stops when its context expires. + +## Run the complete program + +[Capture pane output](../../examples/capture-pane-output/) includes the full +Go program, all imports, project files and a run command. The program +matches `libtmux capture ready` as a complete line, so the echoed command cannot +satisfy the check. It creates and cleans up its own tmux server. + +## Visible pane vs. scrollback + +A capture reads terminal state. Lines that have scrolled beyond retained history +are unavailable, and repeated captures can miss intermediate output. Choose the +range your task needs and use a stream or completion signal when every output +event matters. + + + +## Wait for the expected text + +Use a deadline and a specific output predicate. The example's short polling +pause limits work between checks; it is the observed line that determines +completion. [Sending keys](../sending-keys/) explains why returning from the +input call is not a completion signal. + + + +## Wait for a completion signal + +A program can also signal a dedicated tmux channel. Use the same server endpoint +for the sender and waiter and a new channel name per task. +[Waiting and retrying](/topics/waiting-and-retry/) documents this port's waiting +APIs and failure handling. diff --git a/site/src/content/docs/ports/go/guides/getting-started.md b/site/src/content/docs/ports/go/guides/getting-started.md new file mode 100644 index 00000000..e8e3bcb1 --- /dev/null +++ b/site/src/content/docs/ports/go/guides/getting-started.md @@ -0,0 +1,44 @@ +--- +port: go +route: guides/getting-started +title: Getting started +description: Run a complete program with this language library. +sidebar: + label: Getting started + group: Guides + order: 2 +tableOfContents: true +--- + +Use the `tmux` package to create sessions, send input and read pane output from Go. +The [complete capture program](../../examples/capture-pane-output/) includes +imports, its entry point, project files, dependency setup and a run command. + +## Run the smallest thing that proves it works + +Open that example in an empty directory and save the files using their displayed +names. Its setup pins the library revision that was used to execute the program. +It needs tmux on `PATH` and the native tools named on the example page. + +The program creates a private server, sends a command, waits for the complete +line `libtmux capture ready`, then cleans up. A timeout or command failure is +reported. It does not need an existing tmux session. + +## What just happened + +`Pane.Capture` takes a `tmux.CapturePaneRequest`. Use `Start` and `End` to choose +a range; `tmux.CaptureBoundary` reaches the corresponding history or screen +boundary. The complete program reads the visible screen, compares full lines +and stops when its context expires. + +## Connect to an existing server + +Use the [complete attach program](../attaching-to-tmux/) to select an existing +socket and find the `work` session. That example leaves tmux running; its +launcher owns setup and cleanup for trying it safely. + +## Where to go next + +[Sending keys](../sending-keys/) explains input, and +[Capturing output](../capturing-output/) explains completion. +[Querying and filtering](../querying-and-filtering/) selects a target. diff --git a/site/src/content/docs/ports/go/guides/querying-and-filtering.md b/site/src/content/docs/ports/go/guides/querying-and-filtering.md new file mode 100644 index 00000000..9b31bef1 --- /dev/null +++ b/site/src/content/docs/ports/go/guides/querying-and-filtering.md @@ -0,0 +1,47 @@ +--- +port: go +route: guides/querying-and-filtering +title: Querying and filtering +description: Choose a target and handle missing or ambiguous results. +sidebar: + label: Querying and filtering + group: Guides + order: 6 +tableOfContents: true +--- + +`Server.SearchSessions` accepts a `tmux.TmuxFilter`. The complete program uses +`#{==:#{session_name},work}` and checks the result count before accepting it. +For local collections, `tmuxq.ExactlyOne` distinguishes `ErrNoMatch` from +`ErrMultipleMatches`; preserve those errors when adding context. + + + +## Require exactly one match + +[Attaching to tmux](../attaching-to-tmux/) provides the complete Go program +and its setup. It searches an existing server for `work`, prints the name and +reports an absent session. Its launcher checks that the server remains running. + +For a more general predicate, decide whether zero or several results are valid +before indexing a collection. Keep lookup and command failures visible: another +client can change the server between a read and the operation using its result. + + + +## Declarative filters + +[Filtering and queries](/concepts/queries/) describes this port's query APIs, +accepted fields and result-count contracts. Use that contract when storing a +query in configuration. + +## Case-insensitive matching + +Choose case handling explicitly when your query needs it. The attach program +uses exact case because the intended session is named `work`. + +## Push the filter into tmux, or read once and filter locally + +A tmux-side filter reduces returned rows; a captured collection can answer +several local queries from one read. Neither reserves the result. Check +unexpectedly empty format values before assuming that an object does not exist. diff --git a/site/src/content/docs/ports/go/guides/sending-keys.md b/site/src/content/docs/ports/go/guides/sending-keys.md new file mode 100644 index 00000000..fc257c00 --- /dev/null +++ b/site/src/content/docs/ports/go/guides/sending-keys.md @@ -0,0 +1,44 @@ +--- +port: go +route: guides/sending-keys +title: Sending keys +description: Send text and named keys, then wait for the result. +sidebar: + label: Sending keys + group: Guides + order: 4 +tableOfContents: true +--- + +Pass a `tmux.SendKeysRequest` to `Pane.SendKeys` and check the returned error. +Set `Literal` to send characters and `SkipEnter` to type without submitting. +`Pane.Enter` sends Enter separately. Use a context deadline for operations that +must finish within a fixed budget. + +## Run the complete program + +[Capture pane output](../../examples/capture-pane-output/) supplies the full +Go program, imports, project setup and run command. It starts a private +server, sends a command, checks a complete output line and cleans up. + +## Literal text, key names, and whether Enter follows + +Choose the method and options for the input you mean to send. Literal text +still goes to an application: a shell interprets its quoting, expansions and +commands. Key-name handling and shell interpretation are separate concerns. + +## The race you can't see from the call site + +A successful send means tmux accepted input. The pane's program may still be +starting or processing that input. Wait for the state the next operation needs. +Terminal echo alone does not prove command completion. + +`Pane.Capture` takes a `tmux.CapturePaneRequest`. Use `Start` and `End` to choose +a range; `tmux.CaptureBoundary` reaches the corresponding history or screen +boundary. The complete program reads the visible screen, compares full lines +and stops when its context expires. + +## Where to go next + +[Capturing output](../capturing-output/) covers the output side. +[Attaching to tmux](../attaching-to-tmux/) connects to a server you already own. diff --git a/site/src/content/docs/ports/go/guides/testing-with-libtmux.md b/site/src/content/docs/ports/go/guides/testing-with-libtmux.md new file mode 100644 index 00000000..e9c59408 --- /dev/null +++ b/site/src/content/docs/ports/go/guides/testing-with-libtmux.md @@ -0,0 +1,41 @@ +--- +port: go +route: guides/testing-with-libtmux +title: Testing with libtmux +description: Use an isolated server and check cleanup failures. +sidebar: + label: Testing with libtmux + group: Guides + order: 7 +tableOfContents: true +--- + +`tmuxtest.NewServer` gives a test its own server and cleanup. Call +`tmuxtest.Main` from `TestMain` before using the package's helpers. +`WaitForShellReady` waits for shell startup; `WaitForLine` waits for a complete +output line. The complete capture program shows the corresponding explicit +server ownership and context deadline outside a test suite. + +## Run a complete example + +[Capture pane output](../../examples/capture-pane-output/) includes a complete +Go executable, imports, dependency setup and cleanup. Its output check fails +when the expected line does not arrive before the deadline. Start with that +program when adapting the pattern to your own test runner. + +## Keep server ownership explicit + +Give each test a private socket and a known tmux configuration. Stop the server +the test creates, including when startup or an assertion fails. Preserve the +original failure and report cleanup errors so a leaked server remains visible. + +A connection to an existing server has a different lifetime. The +[attach program](../attaching-to-tmux/) leaves that server running and lets its +launcher own cleanup. + +## Wait for the state you assert + +Wait for the actual output or completion condition with a deadline. +[Sending keys](../sending-keys/) returning successfully does not establish +that the application finished. [Capturing output](../capturing-output/) explains +the difference between a screen snapshot and a stream of output. diff --git a/site/src/content/docs/ports/java/guides/capturing-output.md b/site/src/content/docs/ports/java/guides/capturing-output.md new file mode 100644 index 00000000..9e7ba964 --- /dev/null +++ b/site/src/content/docs/ports/java/guides/capturing-output.md @@ -0,0 +1,48 @@ +--- +port: java +route: guides/capturing-output +title: Capturing output +description: Read pane output with an explicit completion condition. +sidebar: + label: Capturing output + group: Guides + order: 5 +tableOfContents: true +--- + +`Pane.capture` returns visible pane contents as a list of lines. The complete +program compares a full line with its expected value and checks a monotonic +deadline. A `finally` block kills its private tmux server, while +try-with-resources closes the `Server` handle. + +## Run the complete program + +[Capture pane output](../../examples/capture-pane-output/) includes the full +Java program, all imports, project files and a run command. The program +matches `libtmux capture ready` as a complete line, so the echoed command cannot +satisfy the check. It creates and cleans up its own tmux server. + +## Visible pane vs. scrollback + +A capture reads terminal state. Lines that have scrolled beyond retained history +are unavailable, and repeated captures can miss intermediate output. Choose the +range your task needs and use a stream or completion signal when every output +event matters. + + + +## Wait for the expected text + +Use a deadline and a specific output predicate. The example's short polling +pause limits work between checks; it is the observed line that determines +completion. [Sending keys](../sending-keys/) explains why returning from the +input call is not a completion signal. + + + +## Wait for a completion signal + +A program can also signal a dedicated tmux channel. Use the same server endpoint +for the sender and waiter and a new channel name per task. +[Waiting and retrying](/topics/waiting-and-retry/) documents this port's waiting +APIs and failure handling. diff --git a/site/src/content/docs/ports/java/guides/getting-started.md b/site/src/content/docs/ports/java/guides/getting-started.md new file mode 100644 index 00000000..e7e5a6ac --- /dev/null +++ b/site/src/content/docs/ports/java/guides/getting-started.md @@ -0,0 +1,44 @@ +--- +port: java +route: guides/getting-started +title: Getting started +description: Run a complete program with this language library. +sidebar: + label: Getting started + group: Guides + order: 2 +tableOfContents: true +--- + +Use `Server` to create sessions, send input and read pane output from Java. +The [complete capture program](../../examples/capture-pane-output/) includes +imports, its entry point, project files, dependency setup and a run command. + +## Run the smallest thing that proves it works + +Open that example in an empty directory and save the files using their displayed +names. Its setup pins the library revision that was used to execute the program. +It needs tmux on `PATH` and the native tools named on the example page. + +The program creates a private server, sends a command, waits for the complete +line `libtmux capture ready`, then cleans up. A timeout or command failure is +reported. It does not need an existing tmux session. + +## What just happened + +`Pane.capture` returns visible pane contents as a list of lines. The complete +program compares a full line with its expected value and checks a monotonic +deadline. A `finally` block kills its private tmux server, while +try-with-resources closes the `Server` handle. + +## Connect to an existing server + +Use the [complete attach program](../attaching-to-tmux/) to select an existing +socket and find the `work` session. That example leaves tmux running; its +launcher owns setup and cleanup for trying it safely. + +## Where to go next + +[Sending keys](../sending-keys/) explains input, and +[Capturing output](../capturing-output/) explains completion. +[Querying and filtering](../querying-and-filtering/) selects a target. diff --git a/site/src/content/docs/ports/java/guides/querying-and-filtering.md b/site/src/content/docs/ports/java/guides/querying-and-filtering.md new file mode 100644 index 00000000..454c65a1 --- /dev/null +++ b/site/src/content/docs/ports/java/guides/querying-and-filtering.md @@ -0,0 +1,47 @@ +--- +port: java +route: guides/querying-and-filtering +title: Querying and filtering +description: Choose a target and handle missing or ambiguous results. +sidebar: + label: Querying and filtering + group: Guides + order: 6 +tableOfContents: true +--- + +`Server.sessions` returns the sessions to inspect. The complete program +filters the returned stream using `Session.name().equals("work")` and throws +when the session is absent. Session names are unique within one server. +Closing that borrowed `Server` handle leaves the tmux process running. + + + +## Require exactly one match + +[Attaching to tmux](../attaching-to-tmux/) provides the complete Java program +and its setup. It searches an existing server for `work`, prints the name and +reports an absent session. Its launcher checks that the server remains running. + +For a more general predicate, decide whether zero or several results are valid +before indexing a collection. Keep lookup and command failures visible: another +client can change the server between a read and the operation using its result. + + + +## Declarative filters + +[Filtering and queries](/concepts/queries/) describes this port's query APIs, +accepted fields and result-count contracts. Use that contract when storing a +query in configuration. + +## Case-insensitive matching + +Choose case handling explicitly when your query needs it. The attach program +uses exact case because the intended session is named `work`. + +## Push the filter into tmux, or read once and filter locally + +A tmux-side filter reduces returned rows; a captured collection can answer +several local queries from one read. Neither reserves the result. Check +unexpectedly empty format values before assuming that an object does not exist. diff --git a/site/src/content/docs/ports/java/guides/sending-keys.md b/site/src/content/docs/ports/java/guides/sending-keys.md new file mode 100644 index 00000000..01b939bc --- /dev/null +++ b/site/src/content/docs/ports/java/guides/sending-keys.md @@ -0,0 +1,44 @@ +--- +port: java +route: guides/sending-keys +title: Sending keys +description: Send text and named keys, then wait for the result. +sidebar: + label: Sending keys + group: Guides + order: 4 +tableOfContents: true +--- + +`Pane.sendLine` sends a command line and submits it. The complete program +checks the resulting output separately; a successful send does not establish +that the shell finished. Configure a timeout through `ServerConfig` and keep +exceptions visible. + +## Run the complete program + +[Capture pane output](../../examples/capture-pane-output/) supplies the full +Java program, imports, project setup and run command. It starts a private +server, sends a command, checks a complete output line and cleans up. + +## Literal text, key names, and whether Enter follows + +Choose the method and options for the input you mean to send. Literal text +still goes to an application: a shell interprets its quoting, expansions and +commands. Key-name handling and shell interpretation are separate concerns. + +## The race you can't see from the call site + +A successful send means tmux accepted input. The pane's program may still be +starting or processing that input. Wait for the state the next operation needs. +Terminal echo alone does not prove command completion. + +`Pane.capture` returns visible pane contents as a list of lines. The complete +program compares a full line with its expected value and checks a monotonic +deadline. A `finally` block kills its private tmux server, while +try-with-resources closes the `Server` handle. + +## Where to go next + +[Capturing output](../capturing-output/) covers the output side. +[Attaching to tmux](../attaching-to-tmux/) connects to a server you already own. diff --git a/site/src/content/docs/ports/java/guides/testing-with-libtmux.md b/site/src/content/docs/ports/java/guides/testing-with-libtmux.md new file mode 100644 index 00000000..e76a4c5f --- /dev/null +++ b/site/src/content/docs/ports/java/guides/testing-with-libtmux.md @@ -0,0 +1,50 @@ +--- +port: java +route: guides/testing-with-libtmux +title: Testing with libtmux +description: Use an isolated server and check cleanup failures. +sidebar: + label: Testing with libtmux + group: Guides + order: 7 +tableOfContents: true +--- + +`libtmux-junit5` supplies a `TmuxExtension` and a running `Server` for each test. +Request a `TmuxSocketPath` when the code under test accepts a path instead. +For a standalone executable, the complete capture program demonstrates +explicit `ServerConfig` setup, output assertions and cleanup. + + + +The port's `docs` module compiles Java fences from READMEs and guides, then runs +them against `libtmux-junit5` servers. A `` directive can +instead require a named exception, a compile failure, or an explicit skip reason. +Source: `docs/README.md`. + +A setup added by a test fixture is still needed when copying a fragment into a +new project; the linked program includes all imports and its entry point. + +## Run a complete example + +[Capture pane output](../../examples/capture-pane-output/) includes a complete +Java executable, imports, dependency setup and cleanup. Its output check fails +when the expected line does not arrive before the deadline. Start with that +program when adapting the pattern to your own test runner. + +## Keep server ownership explicit + +Give each test a private socket and a known tmux configuration. Stop the server +the test creates, including when startup or an assertion fails. Preserve the +original failure and report cleanup errors so a leaked server remains visible. + +A connection to an existing server has a different lifetime. The +[attach program](../attaching-to-tmux/) leaves that server running and lets its +launcher own cleanup. + +## Wait for the state you assert + +Wait for the actual output or completion condition with a deadline. +[Sending keys](../sending-keys/) returning successfully does not establish +that the application finished. [Capturing output](../capturing-output/) explains +the difference between a screen snapshot and a stream of output. diff --git a/site/src/content/docs/ports/py/guides/capturing-output.md b/site/src/content/docs/ports/py/guides/capturing-output.md new file mode 100644 index 00000000..95a57e6d --- /dev/null +++ b/site/src/content/docs/ports/py/guides/capturing-output.md @@ -0,0 +1,47 @@ +--- +port: py +route: guides/capturing-output +title: Capturing output +description: Read pane output with an explicit completion condition. +sidebar: + label: Capturing output + group: Guides + order: 5 +tableOfContents: true +--- + +`Pane.capture_pane` returns a list of lines. The complete program compares +whole lines, uses a monotonic deadline and closes its private server in +`finally`. The echoed shell command cannot satisfy its output check. + +## Run the complete program + +[Capture pane output](../../examples/capture-pane-output/) includes the full +Python program, all imports, project files and a run command. The program +matches `libtmux capture ready` as a complete line, so the echoed command cannot +satisfy the check. It creates and cleans up its own tmux server. + +## Visible pane vs. scrollback + +A capture reads terminal state. Lines that have scrolled beyond retained history +are unavailable, and repeated captures can miss intermediate output. Choose the +range your task needs and use a stream or completion signal when every output +event matters. + + + +## Wait for the expected text + +Use a deadline and a specific output predicate. The example's short polling +pause limits work between checks; it is the observed line that determines +completion. [Sending keys](../sending-keys/) explains why returning from the +input call is not a completion signal. + + + +## Wait for a completion signal + +A program can also signal a dedicated tmux channel. Use the same server endpoint +for the sender and waiter and a new channel name per task. +[Waiting and retrying](/topics/waiting-and-retry/) documents this port's waiting +APIs and failure handling. diff --git a/site/src/content/docs/ports/py/guides/getting-started.md b/site/src/content/docs/ports/py/guides/getting-started.md new file mode 100644 index 00000000..ed6e079f --- /dev/null +++ b/site/src/content/docs/ports/py/guides/getting-started.md @@ -0,0 +1,43 @@ +--- +port: py +route: guides/getting-started +title: Getting started +description: Run a complete program with this language library. +sidebar: + label: Getting started + group: Guides + order: 2 +tableOfContents: true +--- + +Use `libtmux` to create sessions, send input and read pane output from Python. +The [complete capture program](../../examples/capture-pane-output/) includes +imports, its entry point, project files, dependency setup and a run command. + +## Run the smallest thing that proves it works + +Open that example in an empty directory and save the files using their displayed +names. Its setup pins the library revision that was used to execute the program. +It needs tmux on `PATH` and the native tools named on the example page. + +The program creates a private server, sends a command, waits for the complete +line `libtmux capture ready`, then cleans up. A timeout or command failure is +reported. It does not need an existing tmux session. + +## What just happened + +`Pane.capture_pane` returns a list of lines. The complete program compares +whole lines, uses a monotonic deadline and closes its private server in +`finally`. The echoed shell command cannot satisfy its output check. + +## Connect to an existing server + +Use the [complete attach program](../attaching-to-tmux/) to select an existing +socket and find the `work` session. That example leaves tmux running; its +launcher owns setup and cleanup for trying it safely. + +## Where to go next + +[Sending keys](../sending-keys/) explains input, and +[Capturing output](../capturing-output/) explains completion. +[Querying and filtering](../querying-and-filtering/) selects a target. diff --git a/site/src/content/docs/ports/py/guides/querying-and-filtering.md b/site/src/content/docs/ports/py/guides/querying-and-filtering.md new file mode 100644 index 00000000..fed5f41e --- /dev/null +++ b/site/src/content/docs/ports/py/guides/querying-and-filtering.md @@ -0,0 +1,46 @@ +--- +port: py +route: guides/querying-and-filtering +title: Querying and filtering +description: Choose a target and handle missing or ambiguous results. +sidebar: + label: Querying and filtering + group: Guides + order: 6 +tableOfContents: true +--- + +`server.sessions.get(session_name="work")` selects by exact name and reports +an absent result. The complete program below uses that lookup, prints the +session name and leaves the existing server running. + + + +## Require exactly one match + +[Attaching to tmux](../attaching-to-tmux/) provides the complete Python program +and its setup. It searches an existing server for `work`, prints the name and +reports an absent session. Its launcher checks that the server remains running. + +For a more general predicate, decide whether zero or several results are valid +before indexing a collection. Keep lookup and command failures visible: another +client can change the server between a read and the operation using its result. + + + +## Declarative filters + +[Filtering and queries](/concepts/queries/) describes this port's query APIs, +accepted fields and result-count contracts. Use that contract when storing a +query in configuration. + +## Case-insensitive matching + +Choose case handling explicitly when your query needs it. The attach program +uses exact case because the intended session is named `work`. + +## Push the filter into tmux, or read once and filter locally + +A tmux-side filter reduces returned rows; a captured collection can answer +several local queries from one read. Neither reserves the result. Check +unexpectedly empty format values before assuming that an object does not exist. diff --git a/site/src/content/docs/ports/py/guides/sending-keys.md b/site/src/content/docs/ports/py/guides/sending-keys.md new file mode 100644 index 00000000..52eb13c5 --- /dev/null +++ b/site/src/content/docs/ports/py/guides/sending-keys.md @@ -0,0 +1,42 @@ +--- +port: py +route: guides/sending-keys +title: Sending keys +description: Send text and named keys, then wait for the result. +sidebar: + label: Sending keys + group: Guides + order: 4 +tableOfContents: true +--- + +`Pane.send_keys` accepts literal text with `literal=True`. It sends Enter by +default; use `enter=False` to type without submitting, then call `Pane.enter`. +The complete example sets literal mode explicitly. + +## Run the complete program + +[Capture pane output](../../examples/capture-pane-output/) supplies the full +Python program, imports, project setup and run command. It starts a private +server, sends a command, checks a complete output line and cleans up. + +## Literal text, key names, and whether Enter follows + +Choose the method and options for the input you mean to send. Literal text +still goes to an application: a shell interprets its quoting, expansions and +commands. Key-name handling and shell interpretation are separate concerns. + +## The race you can't see from the call site + +A successful send means tmux accepted input. The pane's program may still be +starting or processing that input. Wait for the state the next operation needs. +Terminal echo alone does not prove command completion. + +`Pane.capture_pane` returns a list of lines. The complete program compares +whole lines, uses a monotonic deadline and closes its private server in +`finally`. The echoed shell command cannot satisfy its output check. + +## Where to go next + +[Capturing output](../capturing-output/) covers the output side. +[Attaching to tmux](../attaching-to-tmux/) connects to a server you already own. diff --git a/site/src/content/docs/ports/py/guides/testing-with-libtmux.md b/site/src/content/docs/ports/py/guides/testing-with-libtmux.md new file mode 100644 index 00000000..b6c5e7ab --- /dev/null +++ b/site/src/content/docs/ports/py/guides/testing-with-libtmux.md @@ -0,0 +1,40 @@ +--- +port: py +route: guides/testing-with-libtmux +title: Testing with libtmux +description: Use an isolated server and check cleanup failures. +sidebar: + label: Testing with libtmux + group: Guides + order: 7 +tableOfContents: true +--- + +The pytest plugin supplies `server` and `session` fixtures. A session fixture +also creates its server. Use `session_params` for fixture setup options. +For a standalone program, the complete capture example uses an explicit private +socket and `finally` cleanup. + +## Run a complete example + +[Capture pane output](../../examples/capture-pane-output/) includes a complete +Python executable, imports, dependency setup and cleanup. Its output check fails +when the expected line does not arrive before the deadline. Start with that +program when adapting the pattern to your own test runner. + +## Keep server ownership explicit + +Give each test a private socket and a known tmux configuration. Stop the server +the test creates, including when startup or an assertion fails. Preserve the +original failure and report cleanup errors so a leaked server remains visible. + +A connection to an existing server has a different lifetime. The +[attach program](../attaching-to-tmux/) leaves that server running and lets its +launcher own cleanup. + +## Wait for the state you assert + +Wait for the actual output or completion condition with a deadline. +[Sending keys](../sending-keys/) returning successfully does not establish +that the application finished. [Capturing output](../capturing-output/) explains +the difference between a screen snapshot and a stream of output. diff --git a/site/src/content/docs/ports/rs/guides/capturing-output.md b/site/src/content/docs/ports/rs/guides/capturing-output.md new file mode 100644 index 00000000..bb11bc64 --- /dev/null +++ b/site/src/content/docs/ports/rs/guides/capturing-output.md @@ -0,0 +1,48 @@ +--- +port: rs +route: guides/capturing-output +title: Capturing output +description: Read pane output with an explicit completion condition. +sidebar: + label: Capturing output + group: Guides + order: 5 +tableOfContents: true +--- + +`Pane::capture` reads the visible screen. The complete program compares the +returned line bytes with the expected output and uses [`tokio::time::timeout`](https://docs.rs/tokio/latest/tokio/time/fn.timeout.html) +to bound the task. It kills its private server and shuts down the client before +returning a capture error. + +## Run the complete program + +[Capture pane output](../../examples/capture-pane-output/) includes the full +Rust program, all imports, project files and a run command. The program +matches `libtmux capture ready` as a complete line, so the echoed command cannot +satisfy the check. It creates and cleans up its own tmux server. + +## Visible pane vs. scrollback + +A capture reads terminal state. Lines that have scrolled beyond retained history +are unavailable, and repeated captures can miss intermediate output. Choose the +range your task needs and use a stream or completion signal when every output +event matters. + + + +## Wait for the expected text + +Use a deadline and a specific output predicate. The example's short polling +pause limits work between checks; it is the observed line that determines +completion. [Sending keys](../sending-keys/) explains why returning from the +input call is not a completion signal. + + + +## Wait for a completion signal + +A program can also signal a dedicated tmux channel. Use the same server endpoint +for the sender and waiter and a new channel name per task. +[Waiting and retrying](/topics/waiting-and-retry/) documents this port's waiting +APIs and failure handling. diff --git a/site/src/content/docs/ports/rs/guides/getting-started.md b/site/src/content/docs/ports/rs/guides/getting-started.md new file mode 100644 index 00000000..e01d1298 --- /dev/null +++ b/site/src/content/docs/ports/rs/guides/getting-started.md @@ -0,0 +1,44 @@ +--- +port: rs +route: guides/getting-started +title: Getting started +description: Run a complete program with this language library. +sidebar: + label: Getting started + group: Guides + order: 2 +tableOfContents: true +--- + +Use `libtmux` to create sessions, send input and read pane output from Rust. +The [complete capture program](../../examples/capture-pane-output/) includes +imports, its entry point, project files, dependency setup and a run command. + +## Run the smallest thing that proves it works + +Open that example in an empty directory and save the files using their displayed +names. Its setup pins the library revision that was used to execute the program. +It needs tmux on `PATH` and the native tools named on the example page. + +The program creates a private server, sends a command, waits for the complete +line `libtmux capture ready`, then cleans up. A timeout or command failure is +reported. It does not need an existing tmux session. + +## What just happened + +`Pane::capture` reads the visible screen. The complete program compares the +returned line bytes with the expected output and uses [`tokio::time::timeout`](https://docs.rs/tokio/latest/tokio/time/fn.timeout.html) +to bound the task. It kills its private server and shuts down the client before +returning a capture error. + +## Connect to an existing server + +Use the [complete attach program](../attaching-to-tmux/) to select an existing +socket and find the `work` session. That example leaves tmux running; its +launcher owns setup and cleanup for trying it safely. + +## Where to go next + +[Sending keys](../sending-keys/) explains input, and +[Capturing output](../capturing-output/) explains completion. +[Querying and filtering](../querying-and-filtering/) selects a target. diff --git a/site/src/content/docs/ports/rs/guides/querying-and-filtering.md b/site/src/content/docs/ports/rs/guides/querying-and-filtering.md new file mode 100644 index 00000000..bb05cef6 --- /dev/null +++ b/site/src/content/docs/ports/rs/guides/querying-and-filtering.md @@ -0,0 +1,47 @@ +--- +port: rs +route: guides/querying-and-filtering +title: Querying and filtering +description: Choose a target and handle missing or ambiguous results. +sidebar: + label: Querying and filtering + group: Guides + order: 6 +tableOfContents: true +--- + +`Server::sessions` returns session handles. The complete program compares +`Session::name` bytes with `b"work"` and reports an absent match. Session names +are unique within that server. It shuts down its client while leaving tmux +running. + + + +## Require exactly one match + +[Attaching to tmux](../attaching-to-tmux/) provides the complete Rust program +and its setup. It searches an existing server for `work`, prints the name and +reports an absent session. Its launcher checks that the server remains running. + +For a more general predicate, decide whether zero or several results are valid +before indexing a collection. Keep lookup and command failures visible: another +client can change the server between a read and the operation using its result. + + + +## Declarative filters + +[Filtering and queries](/concepts/queries/) describes this port's query APIs, +accepted fields and result-count contracts. Use that contract when storing a +query in configuration. + +## Case-insensitive matching + +Choose case handling explicitly when your query needs it. The attach program +uses exact case because the intended session is named `work`. + +## Push the filter into tmux, or read once and filter locally + +A tmux-side filter reduces returned rows; a captured collection can answer +several local queries from one read. Neither reserves the result. Check +unexpectedly empty format values before assuming that an object does not exist. diff --git a/site/src/content/docs/ports/rs/guides/sending-keys.md b/site/src/content/docs/ports/rs/guides/sending-keys.md new file mode 100644 index 00000000..4335bb18 --- /dev/null +++ b/site/src/content/docs/ports/rs/guides/sending-keys.md @@ -0,0 +1,43 @@ +--- +port: rs +route: guides/sending-keys +title: Sending keys +description: Send text and named keys, then wait for the result. +sidebar: + label: Sending keys + group: Guides + order: 4 +tableOfContents: true +--- + +`Pane::send_keys` sends literal text without Enter. `Pane::send_line` sends a +line and Enter in one dispatch. Use `Pane::send_key_names` for named keys. +Await the result and propagate a failed send before attempting the next step. + +## Run the complete program + +[Capture pane output](../../examples/capture-pane-output/) supplies the full +Rust program, imports, project setup and run command. It starts a private +server, sends a command, checks a complete output line and cleans up. + +## Literal text, key names, and whether Enter follows + +Choose the method and options for the input you mean to send. Literal text +still goes to an application: a shell interprets its quoting, expansions and +commands. Key-name handling and shell interpretation are separate concerns. + +## The race you can't see from the call site + +A successful send means tmux accepted input. The pane's program may still be +starting or processing that input. Wait for the state the next operation needs. +Terminal echo alone does not prove command completion. + +`Pane::capture` reads the visible screen. The complete program compares the +returned line bytes with the expected output and uses [`tokio::time::timeout`](https://docs.rs/tokio/latest/tokio/time/fn.timeout.html) +to bound the task. It kills its private server and shuts down the client before +returning a capture error. + +## Where to go next + +[Capturing output](../capturing-output/) covers the output side. +[Attaching to tmux](../attaching-to-tmux/) connects to a server you already own. diff --git a/site/src/content/docs/ports/rs/guides/testing-with-libtmux.md b/site/src/content/docs/ports/rs/guides/testing-with-libtmux.md new file mode 100644 index 00000000..2f37cd34 --- /dev/null +++ b/site/src/content/docs/ports/rs/guides/testing-with-libtmux.md @@ -0,0 +1,41 @@ +--- +port: rs +route: guides/testing-with-libtmux +title: Testing with libtmux +description: Use an isolated server and check cleanup failures. +sidebar: + label: Testing with libtmux + group: Guides + order: 7 +tableOfContents: true +--- + +The `test-support` feature exposes `TestServer` and asynchronous wait helpers. +`TestServer` is acceptable in examples for now. Application examples will move +to the regular [`Server`](../../reference/server-server/) object. The complete +capture program already uses `Server::builder` with an explicit private socket +and performs its own shutdown. + +## Run a complete example + +[Capture pane output](../../examples/capture-pane-output/) includes a complete +Rust executable, imports, dependency setup and cleanup. Its output check fails +when the expected line does not arrive before the deadline. Start with that +program when adapting the pattern to your own test runner. + +## Keep server ownership explicit + +Give each test a private socket and a known tmux configuration. Stop the server +the test creates, including when startup or an assertion fails. Preserve the +original failure and report cleanup errors so a leaked server remains visible. + +A connection to an existing server has a different lifetime. The +[attach program](../attaching-to-tmux/) leaves that server running and lets its +launcher own cleanup. + +## Wait for the state you assert + +Wait for the actual output or completion condition with a deadline. +[Sending keys](../sending-keys/) returning successfully does not establish +that the application finished. [Capturing output](../capturing-output/) explains +the difference between a screen snapshot and a stream of output. diff --git a/site/src/content/docs/ports/swift/guides/capturing-output.md b/site/src/content/docs/ports/swift/guides/capturing-output.md new file mode 100644 index 00000000..730a92dd --- /dev/null +++ b/site/src/content/docs/ports/swift/guides/capturing-output.md @@ -0,0 +1,48 @@ +--- +port: swift +route: guides/capturing-output +title: Capturing output +description: Read pane output with an explicit completion condition. +sidebar: + label: Capturing output + group: Guides + order: 5 +tableOfContents: true +--- + +`Server.capture` reads pane lines. The complete program compares a whole line, +bounds the wait and reports cleanup errors. For repeated reads of changing +output, the streaming APIs can track output beyond one screen snapshot; +consult this version's API reference for their lifetime and completion rules. + +## Run the complete program + +[Capture pane output](../../examples/capture-pane-output/) includes the full +Swift program, all imports, project files and a run command. The program +matches `libtmux capture ready` as a complete line, so the echoed command cannot +satisfy the check. It creates and cleans up its own tmux server. + +## Visible pane vs. scrollback + +A capture reads terminal state. Lines that have scrolled beyond retained history +are unavailable, and repeated captures can miss intermediate output. Choose the +range your task needs and use a stream or completion signal when every output +event matters. + + + +## Wait for the expected text + +Use a deadline and a specific output predicate. The example's short polling +pause limits work between checks; it is the observed line that determines +completion. [Sending keys](../sending-keys/) explains why returning from the +input call is not a completion signal. + + + +## Wait for a completion signal + +A program can also signal a dedicated tmux channel. Use the same server endpoint +for the sender and waiter and a new channel name per task. +[Waiting and retrying](/topics/waiting-and-retry/) documents this port's waiting +APIs and failure handling. diff --git a/site/src/content/docs/ports/swift/guides/getting-started.md b/site/src/content/docs/ports/swift/guides/getting-started.md new file mode 100644 index 00000000..8001adb7 --- /dev/null +++ b/site/src/content/docs/ports/swift/guides/getting-started.md @@ -0,0 +1,44 @@ +--- +port: swift +route: guides/getting-started +title: Getting started +description: Run a complete program with this language library. +sidebar: + label: Getting started + group: Guides + order: 2 +tableOfContents: true +--- + +Use `Server` to create sessions, send input and read pane output from Swift. +The [complete capture program](../../examples/capture-pane-output/) includes +imports, its entry point, project files, dependency setup and a run command. + +## Run the smallest thing that proves it works + +Open that example in an empty directory and save the files using their displayed +names. Its setup pins the library revision that was used to execute the program. +It needs tmux on `PATH` and the native tools named on the example page. + +The program creates a private server, sends a command, waits for the complete +line `libtmux capture ready`, then cleans up. A timeout or command failure is +reported. It does not need an existing tmux session. + +## What just happened + +`Server.capture` reads pane lines. The complete program compares a whole line, +bounds the wait and reports cleanup errors. For repeated reads of changing +output, the streaming APIs can track output beyond one screen snapshot; +consult this version's API reference for their lifetime and completion rules. + +## Connect to an existing server + +Use the [complete attach program](../attaching-to-tmux/) to select an existing +socket and find the `work` session. That example leaves tmux running; its +launcher owns setup and cleanup for trying it safely. + +## Where to go next + +[Sending keys](../sending-keys/) explains input, and +[Capturing output](../capturing-output/) explains completion. +[Querying and filtering](../querying-and-filtering/) selects a target. diff --git a/site/src/content/docs/ports/swift/guides/querying-and-filtering.md b/site/src/content/docs/ports/swift/guides/querying-and-filtering.md new file mode 100644 index 00000000..8c692ce0 --- /dev/null +++ b/site/src/content/docs/ports/swift/guides/querying-and-filtering.md @@ -0,0 +1,47 @@ +--- +port: swift +route: guides/querying-and-filtering +title: Querying and filtering +description: Choose a target and handle missing or ambiguous results. +sidebar: + label: Querying and filtering + group: Guides + order: 6 +tableOfContents: true +--- + +`Server.hasSession` checks existence. The complete program passes `"=work"` +to require the exact tmux session name, reports an absent session and leaves +the server running. An existence check does not return a session handle or +reserve the session against changes by another client. + + + +## Require exactly one match + +[Attaching to tmux](../attaching-to-tmux/) provides the complete Swift program +and its setup. It searches an existing server for `work`, prints the name and +reports an absent session. Its launcher checks that the server remains running. + +For a more general predicate, decide whether zero or several results are valid +before indexing a collection. Keep lookup and command failures visible: another +client can change the server between a read and the operation using its result. + + + +## Declarative filters + +[Filtering and queries](/concepts/queries/) describes this port's query APIs, +accepted fields and result-count contracts. Use that contract when storing a +query in configuration. + +## Case-insensitive matching + +Choose case handling explicitly when your query needs it. The attach program +uses exact case because the intended session is named `work`. + +## Push the filter into tmux, or read once and filter locally + +A tmux-side filter reduces returned rows; a captured collection can answer +several local queries from one read. Neither reserves the result. Check +unexpectedly empty format values before assuming that an object does not exist. diff --git a/site/src/content/docs/ports/swift/guides/sending-keys.md b/site/src/content/docs/ports/swift/guides/sending-keys.md new file mode 100644 index 00000000..e9df6d08 --- /dev/null +++ b/site/src/content/docs/ports/swift/guides/sending-keys.md @@ -0,0 +1,43 @@ +--- +port: swift +route: guides/sending-keys +title: Sending keys +description: Send text and named keys, then wait for the result. +sidebar: + label: Sending keys + group: Guides + order: 4 +tableOfContents: true +--- + +`Server.run(_:in:)` types a command line and presses Enter. Await it with +`try await`, then wait separately for the output your next operation needs. +The complete program uses an explicit socket and propagates failed operations. + +## Run the complete program + +[Capture pane output](../../examples/capture-pane-output/) supplies the full +Swift program, imports, project setup and run command. It starts a private +server, sends a command, checks a complete output line and cleans up. + +## Literal text, key names, and whether Enter follows + +Choose the method and options for the input you mean to send. Literal text +still goes to an application: a shell interprets its quoting, expansions and +commands. Key-name handling and shell interpretation are separate concerns. + +## The race you can't see from the call site + +A successful send means tmux accepted input. The pane's program may still be +starting or processing that input. Wait for the state the next operation needs. +Terminal echo alone does not prove command completion. + +`Server.capture` reads pane lines. The complete program compares a whole line, +bounds the wait and reports cleanup errors. For repeated reads of changing +output, the streaming APIs can track output beyond one screen snapshot; +consult this version's API reference for their lifetime and completion rules. + +## Where to go next + +[Capturing output](../capturing-output/) covers the output side. +[Attaching to tmux](../attaching-to-tmux/) connects to a server you already own. diff --git a/site/src/content/docs/ports/swift/guides/testing-with-libtmux.md b/site/src/content/docs/ports/swift/guides/testing-with-libtmux.md new file mode 100644 index 00000000..5adb4f09 --- /dev/null +++ b/site/src/content/docs/ports/swift/guides/testing-with-libtmux.md @@ -0,0 +1,41 @@ +--- +port: swift +route: guides/testing-with-libtmux +title: Testing with libtmux +description: Use an isolated server and check cleanup failures. +sidebar: + label: Testing with libtmux + group: Guides + order: 7 +tableOfContents: true +--- + +[TmuxFixture](https://github.com/libtmux/libtmux-swift/tree/254f8b2be7eb60cacc3ffcb3ea8e456784f582df/Tests/TmuxFixture) +is a separate package product. `withTmuxServer` supplies a +private server with a bootstrap session for the body of a test. Use a bounded +wait for output assertions. The complete capture program demonstrates explicit +server setup and cleanup outside a testing fixture. + +## Run a complete example + +[Capture pane output](../../examples/capture-pane-output/) includes a complete +Swift executable, imports, dependency setup and cleanup. Its output check fails +when the expected line does not arrive before the deadline. Start with that +program when adapting the pattern to your own test runner. + +## Keep server ownership explicit + +Give each test a private socket and a known tmux configuration. Stop the server +the test creates, including when startup or an assertion fails. Preserve the +original failure and report cleanup errors so a leaked server remains visible. + +A connection to an existing server has a different lifetime. The +[attach program](../attaching-to-tmux/) leaves that server running and lets its +launcher own cleanup. + +## Wait for the state you assert + +Wait for the actual output or completion condition with a deadline. +[Sending keys](../sending-keys/) returning successfully does not establish +that the application finished. [Capturing output](../capturing-output/) explains +the difference between a screen snapshot and a stream of output. diff --git a/site/src/content/docs/ports/ts/guides/capturing-output.md b/site/src/content/docs/ports/ts/guides/capturing-output.md new file mode 100644 index 00000000..a2a372d3 --- /dev/null +++ b/site/src/content/docs/ports/ts/guides/capturing-output.md @@ -0,0 +1,48 @@ +--- +port: ts +route: guides/capturing-output +title: Capturing output +description: Read pane output with an explicit completion condition. +sidebar: + label: Capturing output + group: Guides + order: 5 +tableOfContents: true +--- + +`Pane.capture` returns captured lines. The complete program uses +[`AbortSignal.timeout`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static) for a deadline and matches a whole output line. +Its cleanup checks whether the private socket exists, including when startup +fails, and reports shutdown errors. + +## Run the complete program + +[Capture pane output](../../examples/capture-pane-output/) includes the full +TypeScript program, all imports, project files and a run command. The program +matches `libtmux capture ready` as a complete line, so the echoed command cannot +satisfy the check. It creates and cleans up its own tmux server. + +## Visible pane vs. scrollback + +A capture reads terminal state. Lines that have scrolled beyond retained history +are unavailable, and repeated captures can miss intermediate output. Choose the +range your task needs and use a stream or completion signal when every output +event matters. + + + +## Wait for the expected text + +Use a deadline and a specific output predicate. The example's short polling +pause limits work between checks; it is the observed line that determines +completion. [Sending keys](../sending-keys/) explains why returning from the +input call is not a completion signal. + + + +## Wait for a completion signal + +A program can also signal a dedicated tmux channel. Use the same server endpoint +for the sender and waiter and a new channel name per task. +[Waiting and retrying](/topics/waiting-and-retry/) documents this port's waiting +APIs and failure handling. diff --git a/site/src/content/docs/ports/ts/guides/getting-started.md b/site/src/content/docs/ports/ts/guides/getting-started.md new file mode 100644 index 00000000..e1fc417b --- /dev/null +++ b/site/src/content/docs/ports/ts/guides/getting-started.md @@ -0,0 +1,44 @@ +--- +port: ts +route: guides/getting-started +title: Getting started +description: Run a complete program with this language library. +sidebar: + label: Getting started + group: Guides + order: 2 +tableOfContents: true +--- + +Use `libtmux` to create sessions, send input and read pane output from TypeScript. +The [complete capture program](../../examples/capture-pane-output/) includes +imports, its entry point, project files, dependency setup and a run command. + +## Run the smallest thing that proves it works + +Open that example in an empty directory and save the files using their displayed +names. Its setup pins the library revision that was used to execute the program. +It needs tmux on `PATH` and the native tools named on the example page. + +The program creates a private server, sends a command, waits for the complete +line `libtmux capture ready`, then cleans up. A timeout or command failure is +reported. It does not need an existing tmux session. + +## What just happened + +`Pane.capture` returns captured lines. The complete program uses +[`AbortSignal.timeout`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static) for a deadline and matches a whole output line. +Its cleanup checks whether the private socket exists, including when startup +fails, and reports shutdown errors. + +## Connect to an existing server + +Use the [complete attach program](../attaching-to-tmux/) to select an existing +socket and find the `work` session. That example leaves tmux running; its +launcher owns setup and cleanup for trying it safely. + +## Where to go next + +[Sending keys](../sending-keys/) explains input, and +[Capturing output](../capturing-output/) explains completion. +[Querying and filtering](../querying-and-filtering/) selects a target. diff --git a/site/src/content/docs/ports/ts/guides/querying-and-filtering.md b/site/src/content/docs/ports/ts/guides/querying-and-filtering.md new file mode 100644 index 00000000..f06f7f42 --- /dev/null +++ b/site/src/content/docs/ports/ts/guides/querying-and-filtering.md @@ -0,0 +1,47 @@ +--- +port: ts +route: guides/querying-and-filtering +title: Querying and filtering +description: Choose a target and handle missing or ambiguous results. +sidebar: + label: Querying and filtering + group: Guides + order: 6 +tableOfContents: true +--- + +Take a snapshot with `Server.snapshot`, then use `snapshot.sessions.one` with +an exact name predicate. The complete program reports an absent result and +leaves the existing tmux server running. A snapshot describes the read that +created it; later commands can still fail if an object has disappeared. + + + +## Require exactly one match + +[Attaching to tmux](../attaching-to-tmux/) provides the complete TypeScript program +and its setup. It searches an existing server for `work`, prints the name and +reports an absent session. Its launcher checks that the server remains running. + +For a more general predicate, decide whether zero or several results are valid +before indexing a collection. Keep lookup and command failures visible: another +client can change the server between a read and the operation using its result. + + + +## Declarative filters + +[Filtering and queries](/concepts/queries/) describes this port's query APIs, +accepted fields and result-count contracts. Use that contract when storing a +query in configuration. + +## Case-insensitive matching + +Choose case handling explicitly when your query needs it. The attach program +uses exact case because the intended session is named `work`. + +## Push the filter into tmux, or read once and filter locally + +A tmux-side filter reduces returned rows; a captured collection can answer +several local queries from one read. Neither reserves the result. Check +unexpectedly empty format values before assuming that an object does not exist. diff --git a/site/src/content/docs/ports/ts/guides/sending-keys.md b/site/src/content/docs/ports/ts/guides/sending-keys.md new file mode 100644 index 00000000..408b4ce7 --- /dev/null +++ b/site/src/content/docs/ports/ts/guides/sending-keys.md @@ -0,0 +1,43 @@ +--- +port: ts +route: guides/sending-keys +title: Sending keys +description: Send text and named keys, then wait for the result. +sidebar: + label: Sending keys + group: Guides + order: 4 +tableOfContents: true +--- + +`Pane.sendKeys` sends Enter by default. Use `enter: false` to type without +submitting and `literal: true` when characters could be interpreted as key +names. Pass an abort signal to bound the operation. + +## Run the complete program + +[Capture pane output](../../examples/capture-pane-output/) supplies the full +TypeScript program, imports, project setup and run command. It starts a private +server, sends a command, checks a complete output line and cleans up. + +## Literal text, key names, and whether Enter follows + +Choose the method and options for the input you mean to send. Literal text +still goes to an application: a shell interprets its quoting, expansions and +commands. Key-name handling and shell interpretation are separate concerns. + +## The race you can't see from the call site + +A successful send means tmux accepted input. The pane's program may still be +starting or processing that input. Wait for the state the next operation needs. +Terminal echo alone does not prove command completion. + +`Pane.capture` returns captured lines. The complete program uses +[`AbortSignal.timeout`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static) for a deadline and matches a whole output line. +Its cleanup checks whether the private socket exists, including when startup +fails, and reports shutdown errors. + +## Where to go next + +[Capturing output](../capturing-output/) covers the output side. +[Attaching to tmux](../attaching-to-tmux/) connects to a server you already own. diff --git a/site/src/content/docs/ports/ts/guides/testing-with-libtmux.md b/site/src/content/docs/ports/ts/guides/testing-with-libtmux.md new file mode 100644 index 00000000..781f9e81 --- /dev/null +++ b/site/src/content/docs/ports/ts/guides/testing-with-libtmux.md @@ -0,0 +1,40 @@ +--- +port: ts +route: guides/testing-with-libtmux +title: Testing with libtmux +description: Use an isolated server and check cleanup failures. +sidebar: + label: Testing with libtmux + group: Guides + order: 7 +tableOfContents: true +--- + +The repository's internal test harness is not a published API. For an external +test, create a `Server` with a private socket and register cleanup in your test +framework. The complete capture program demonstrates startup checks, a bounded +output wait and visible cleanup failures. + +## Run a complete example + +[Capture pane output](../../examples/capture-pane-output/) includes a complete +TypeScript executable, imports, dependency setup and cleanup. Its output check fails +when the expected line does not arrive before the deadline. Start with that +program when adapting the pattern to your own test runner. + +## Keep server ownership explicit + +Give each test a private socket and a known tmux configuration. Stop the server +the test creates, including when startup or an assertion fails. Preserve the +original failure and report cleanup errors so a leaked server remains visible. + +A connection to an existing server has a different lifetime. The +[attach program](../attaching-to-tmux/) leaves that server running and lets its +launcher own cleanup. + +## Wait for the state you assert + +Wait for the actual output or completion condition with a deadline. +[Sending keys](../sending-keys/) returning successfully does not establish +that the application finished. [Capturing output](../capturing-output/) explains +the difference between a screen snapshot and a stream of output. diff --git a/site/src/data/mentions.json b/site/src/data/mentions.json index a36ea931..b21bdcd6 100644 --- a/site/src/data/mentions.json +++ b/site/src/data/mentions.json @@ -73,8 +73,8 @@ { "port": "cxx", "symbol": "libtmux::exactly_one", - "page": "/guides/querying-and-filtering/", - "title": "Filtering and querying, in practice", + "page": "/cxx/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", "section": "guides" }, { @@ -203,6 +203,27 @@ "title": "Traversal", "section": "topics" }, + { + "port": "cxx", + "symbol": "libtmux::Pane::capture", + "page": "/cxx/latest/guides/capturing-output/", + "title": "Capturing output", + "section": "guides" + }, + { + "port": "cxx", + "symbol": "libtmux::Pane::capture", + "page": "/cxx/latest/guides/getting-started/", + "title": "Getting started", + "section": "guides" + }, + { + "port": "cxx", + "symbol": "libtmux::Pane::capture", + "page": "/cxx/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "cxx", "symbol": "libtmux::Pane::expand", @@ -224,6 +245,13 @@ "title": "Options and hooks", "section": "topics" }, + { + "port": "cxx", + "symbol": "libtmux::Pane::send_key", + "page": "/cxx/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "cxx", "symbol": "libtmux::Pane::send_key", @@ -231,6 +259,20 @@ "title": "Pane interaction", "section": "topics" }, + { + "port": "cxx", + "symbol": "libtmux::Pane::send_line", + "page": "/cxx/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, + { + "port": "cxx", + "symbol": "libtmux::Pane::send_text", + "page": "/cxx/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "cxx", "symbol": "libtmux::Pane::send_text", @@ -273,6 +315,13 @@ "title": "Attaching to tmux", "section": "guides" }, + { + "port": "cxx", + "symbol": "libtmux::Server", + "page": "/cxx/latest/guides/testing-with-libtmux/", + "title": "Testing with libtmux", + "section": "guides" + }, { "port": "cxx", "symbol": "libtmux::Server", @@ -322,6 +371,13 @@ "title": "Socket and servers", "section": "topics" }, + { + "port": "cxx", + "symbol": "libtmux::Server::at_socket_path", + "page": "/cxx/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", + "section": "guides" + }, { "port": "cxx", "symbol": "libtmux::Server::at_socket_path", @@ -364,6 +420,13 @@ "title": "Socket and servers", "section": "topics" }, + { + "port": "cxx", + "symbol": "libtmux::Server::sessions", + "page": "/cxx/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", + "section": "guides" + }, { "port": "cxx", "symbol": "libtmux::Server::sessions", @@ -378,13 +441,6 @@ "title": "Waiting and retrying", "section": "topics" }, - { - "port": "cxx", - "symbol": "libtmux::Server::wait_for", - "page": "/guides/capturing-output/", - "title": "Capturing output", - "section": "guides" - }, { "port": "cxx", "symbol": "libtmux::Server::wait_for", @@ -427,6 +483,13 @@ "title": "Options and hooks", "section": "topics" }, + { + "port": "cxx", + "symbol": "libtmux::Session::name", + "page": "/cxx/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", + "section": "guides" + }, { "port": "cxx", "symbol": "libtmux::Session::panes", @@ -462,6 +525,13 @@ "title": "Capture pane output", "section": "examples" }, + { + "port": "cxx", + "symbol": "libtmux::test::ScopedTmuxServer", + "page": "/cxx/latest/guides/testing-with-libtmux/", + "title": "Testing with libtmux", + "section": "guides" + }, { "port": "cxx", "symbol": "libtmux::test::ScopedTmuxServer", @@ -476,13 +546,6 @@ "title": "Ownership and cleanup", "section": "topics" }, - { - "port": "cxx", - "symbol": "libtmux::test::SocketNamespace::consumer", - "page": "/guides/testing-with-libtmux/", - "title": "Testing with libtmux", - "section": "guides" - }, { "port": "cxx", "symbol": "libtmux::Window", @@ -812,6 +875,27 @@ "title": ".NET MCP API", "section": "ports" }, + { + "port": "dotnet", + "symbol": "LibTmux.OwnedServerScope", + "page": "/dotnet/latest/guides/capturing-output/", + "title": "Capturing output", + "section": "guides" + }, + { + "port": "dotnet", + "symbol": "LibTmux.OwnedServerScope", + "page": "/dotnet/latest/guides/getting-started/", + "title": "Getting started", + "section": "guides" + }, + { + "port": "dotnet", + "symbol": "LibTmux.OwnedServerScope", + "page": "/dotnet/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "dotnet", "symbol": "LibTmux.OwnedSessionScope", @@ -836,10 +920,31 @@ { "port": "dotnet", "symbol": "LibTmux.Pane.CaptureAsync", - "page": "/guides/capturing-output/", + "page": "/dotnet/latest/guides/capturing-output/", "title": "Capturing output", "section": "guides" }, + { + "port": "dotnet", + "symbol": "LibTmux.Pane.CaptureAsync", + "page": "/dotnet/latest/guides/getting-started/", + "title": "Getting started", + "section": "guides" + }, + { + "port": "dotnet", + "symbol": "LibTmux.Pane.CaptureAsync", + "page": "/dotnet/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, + { + "port": "dotnet", + "symbol": "LibTmux.Pane.EnterAsync", + "page": "/dotnet/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "dotnet", "symbol": "LibTmux.Pane.Equals", @@ -868,6 +973,13 @@ "title": "Pane interaction", "section": "topics" }, + { + "port": "dotnet", + "symbol": "LibTmux.Pane.SendTextAsync", + "page": "/dotnet/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "dotnet", "symbol": "LibTmux.Pane.SendTextAsync", @@ -924,6 +1036,13 @@ "title": "Control mode vs one-shot", "section": "concepts" }, + { + "port": "dotnet", + "symbol": "LibTmux.Server.CreateOwnedAsync", + "page": "/dotnet/latest/guides/testing-with-libtmux/", + "title": "Testing with libtmux", + "section": "guides" + }, { "port": "dotnet", "symbol": "LibTmux.Server.CreateOwnedAsync", @@ -952,6 +1071,13 @@ "title": "Traversal", "section": "topics" }, + { + "port": "dotnet", + "symbol": "LibTmux.Server.HasSessionAsync", + "page": "/dotnet/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", + "section": "guides" + }, { "port": "dotnet", "symbol": "LibTmux.Server.IsAliveAsync", @@ -1036,6 +1162,13 @@ "title": "Ownership and cleanup", "section": "topics" }, + { + "port": "dotnet", + "symbol": "LibTmux.Testing.TmuxTestFactory", + "page": "/dotnet/latest/guides/testing-with-libtmux/", + "title": "Testing with libtmux", + "section": "guides" + }, { "port": "dotnet", "symbol": "LibTmux.Testing.TmuxTestFactory.CreateHierarchyAsync", @@ -1183,20 +1316,6 @@ "title": "Errors and exceptions", "section": "topics" }, - { - "port": "dotnet", - "symbol": "LibTmux.TmuxWait.UntilAsync", - "page": "/guides/sending-keys/", - "title": "Sending keys", - "section": "guides" - }, - { - "port": "dotnet", - "symbol": "LibTmux.TmuxWait.UntilAsync", - "page": "/guides/testing-with-libtmux/", - "title": "Testing with libtmux", - "section": "guides" - }, { "port": "dotnet", "symbol": "LibTmux.TmuxWait.UntilAsync", @@ -2009,6 +2128,27 @@ "title": "Go MCP API", "section": "ports" }, + { + "port": "go", + "symbol": "tmux.CaptureBoundary", + "page": "/go/latest/guides/capturing-output/", + "title": "Capturing output", + "section": "guides" + }, + { + "port": "go", + "symbol": "tmux.CaptureBoundary", + "page": "/go/latest/guides/getting-started/", + "title": "Getting started", + "section": "guides" + }, + { + "port": "go", + "symbol": "tmux.CaptureBoundary", + "page": "/go/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "go", "symbol": "tmux.CaptureBoundary", @@ -2016,6 +2156,48 @@ "title": "Pane interaction", "section": "topics" }, + { + "port": "go", + "symbol": "tmux.CapturePaneRequest", + "page": "/go/latest/guides/capturing-output/", + "title": "Capturing output", + "section": "guides" + }, + { + "port": "go", + "symbol": "tmux.CapturePaneRequest", + "page": "/go/latest/guides/getting-started/", + "title": "Getting started", + "section": "guides" + }, + { + "port": "go", + "symbol": "tmux.CapturePaneRequest", + "page": "/go/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, + { + "port": "go", + "symbol": "tmux.CapturePaneRequest.End", + "page": "/go/latest/guides/capturing-output/", + "title": "Capturing output", + "section": "guides" + }, + { + "port": "go", + "symbol": "tmux.CapturePaneRequest.End", + "page": "/go/latest/guides/getting-started/", + "title": "Getting started", + "section": "guides" + }, + { + "port": "go", + "symbol": "tmux.CapturePaneRequest.End", + "page": "/go/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "go", "symbol": "tmux.CommandError.Is", @@ -2100,6 +2282,27 @@ "title": "Ownership and cleanup", "section": "topics" }, + { + "port": "go", + "symbol": "tmux.Pane.Capture", + "page": "/go/latest/guides/capturing-output/", + "title": "Capturing output", + "section": "guides" + }, + { + "port": "go", + "symbol": "tmux.Pane.Capture", + "page": "/go/latest/guides/getting-started/", + "title": "Getting started", + "section": "guides" + }, + { + "port": "go", + "symbol": "tmux.Pane.Capture", + "page": "/go/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "go", "symbol": "tmux.Pane.Capture", @@ -2121,6 +2324,13 @@ "title": "Format-token fields", "section": "topics" }, + { + "port": "go", + "symbol": "tmux.Pane.Enter", + "page": "/go/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "go", "symbol": "tmux.Pane.ID", @@ -2149,6 +2359,13 @@ "title": "Options and hooks", "section": "topics" }, + { + "port": "go", + "symbol": "tmux.Pane.SendKeys", + "page": "/go/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "go", "symbol": "tmux.Pane.Window", @@ -2205,6 +2422,20 @@ "title": "Control mode vs one-shot", "section": "concepts" }, + { + "port": "go", + "symbol": "tmux.SendKeysRequest", + "page": "/go/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, + { + "port": "go", + "symbol": "tmux.SendKeysRequest.SkipEnter", + "page": "/go/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "go", "symbol": "tmux.Server", @@ -2268,6 +2499,13 @@ "title": "Server, session, window, pane", "section": "concepts" }, + { + "port": "go", + "symbol": "tmux.Server.SearchSessions", + "page": "/go/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", + "section": "guides" + }, { "port": "go", "symbol": "tmux.Server.Sessions", @@ -2443,6 +2681,13 @@ "title": "Filtering and queries", "section": "concepts" }, + { + "port": "go", + "symbol": "tmux.TmuxFilter", + "page": "/go/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", + "section": "guides" + }, { "port": "go", "symbol": "tmux.WaitForRequest", @@ -2516,22 +2761,22 @@ { "port": "go", "symbol": "tmuxq.ErrMultipleMatches", - "page": "/guides/querying-and-filtering/", - "title": "Filtering and querying, in practice", + "page": "/go/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", "section": "guides" }, { "port": "go", "symbol": "tmuxq.ErrNoMatch", - "page": "/guides/querying-and-filtering/", - "title": "Filtering and querying, in practice", + "page": "/go/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", "section": "guides" }, { "port": "go", "symbol": "tmuxq.ExactlyOne", - "page": "/guides/querying-and-filtering/", - "title": "Filtering and querying, in practice", + "page": "/go/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", "section": "guides" }, { @@ -2541,24 +2786,17 @@ "title": "Filtering and queries", "section": "concepts" }, - { - "port": "go", - "symbol": "tmuxq.Where", - "page": "/guides/querying-and-filtering/", - "title": "Filtering and querying, in practice", - "section": "guides" - }, { "port": "go", "symbol": "tmuxtest.Main", - "page": "/guides/testing-with-libtmux/", + "page": "/go/latest/guides/testing-with-libtmux/", "title": "Testing with libtmux", "section": "guides" }, { "port": "go", "symbol": "tmuxtest.NewServer", - "page": "/guides/testing-with-libtmux/", + "page": "/go/latest/guides/testing-with-libtmux/", "title": "Testing with libtmux", "section": "guides" }, @@ -2576,11 +2814,18 @@ "title": "Waiting and retrying", "section": "topics" }, + { + "port": "go", + "symbol": "tmuxtest.WaitForLine", + "page": "/go/latest/guides/testing-with-libtmux/", + "title": "Testing with libtmux", + "section": "guides" + }, { "port": "go", "symbol": "tmuxtest.WaitForShellReady", - "page": "/guides/sending-keys/", - "title": "Sending keys", + "page": "/go/latest/guides/testing-with-libtmux/", + "title": "Testing with libtmux", "section": "guides" }, { @@ -2933,10 +3178,17 @@ "title": "Streaming", "section": "guides" }, + { + "port": "java", + "symbol": "io.github.libtmux.junit5.TmuxExtension.TmuxExtension", + "page": "/java/latest/guides/testing-with-libtmux/", + "title": "Testing with libtmux", + "section": "guides" + }, { "port": "java", "symbol": "io.github.libtmux.junit5.TmuxSocketPath.TmuxSocketPath", - "page": "/guides/testing-with-libtmux/", + "page": "/java/latest/guides/testing-with-libtmux/", "title": "Testing with libtmux", "section": "guides" }, @@ -3027,10 +3279,24 @@ { "port": "java", "symbol": "io.github.libtmux.Pane.Pane.capture", - "page": "/guides/capturing-output/", + "page": "/java/latest/guides/capturing-output/", "title": "Capturing output", "section": "guides" }, + { + "port": "java", + "symbol": "io.github.libtmux.Pane.Pane.capture", + "page": "/java/latest/guides/getting-started/", + "title": "Getting started", + "section": "guides" + }, + { + "port": "java", + "symbol": "io.github.libtmux.Pane.Pane.capture", + "page": "/java/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "java", "symbol": "io.github.libtmux.Pane.Pane.equals", @@ -3066,6 +3332,13 @@ "title": "Pane interaction", "section": "topics" }, + { + "port": "java", + "symbol": "io.github.libtmux.Pane.Pane.sendLine", + "page": "/java/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "java", "symbol": "io.github.libtmux.Pane.Pane.sendLine", @@ -3125,15 +3398,43 @@ { "port": "java", "symbol": "io.github.libtmux.Server.Server", - "page": "/guides/testing-with-libtmux/", - "title": "Testing with libtmux", + "page": "/java/latest/guides/attaching-to-tmux/", + "title": "Attaching to tmux", "section": "guides" }, { "port": "java", "symbol": "io.github.libtmux.Server.Server", - "page": "/java/latest/guides/attaching-to-tmux/", - "title": "Attaching to tmux", + "page": "/java/latest/guides/capturing-output/", + "title": "Capturing output", + "section": "guides" + }, + { + "port": "java", + "symbol": "io.github.libtmux.Server.Server", + "page": "/java/latest/guides/getting-started/", + "title": "Getting started", + "section": "guides" + }, + { + "port": "java", + "symbol": "io.github.libtmux.Server.Server", + "page": "/java/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", + "section": "guides" + }, + { + "port": "java", + "symbol": "io.github.libtmux.Server.Server", + "page": "/java/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, + { + "port": "java", + "symbol": "io.github.libtmux.Server.Server", + "page": "/java/latest/guides/testing-with-libtmux/", + "title": "Testing with libtmux", "section": "guides" }, { @@ -3227,6 +3528,13 @@ "title": "Compatibility", "section": "guides" }, + { + "port": "java", + "symbol": "io.github.libtmux.Server.Server.sessions", + "page": "/java/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", + "section": "guides" + }, { "port": "java", "symbol": "io.github.libtmux.Server.Server.sessions", @@ -3241,6 +3549,20 @@ "title": "Compatibility", "section": "guides" }, + { + "port": "java", + "symbol": "io.github.libtmux.ServerConfig.ServerConfig", + "page": "/java/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, + { + "port": "java", + "symbol": "io.github.libtmux.ServerConfig.ServerConfig", + "page": "/java/latest/guides/testing-with-libtmux/", + "title": "Testing with libtmux", + "section": "guides" + }, { "port": "java", "symbol": "io.github.libtmux.ServerConfig.ServerConfig", @@ -3318,6 +3640,13 @@ "title": "Traversal", "section": "topics" }, + { + "port": "java", + "symbol": "io.github.libtmux.Session.Session.name", + "page": "/java/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", + "section": "guides" + }, { "port": "java", "symbol": "io.github.libtmux.Session.Session.windows", @@ -5432,6 +5761,13 @@ "title": "Filtering and queries", "section": "concepts" }, + { + "port": "py", + "symbol": "libtmux._internal.query_list.QueryList.get", + "page": "/py/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", + "section": "guides" + }, { "port": "py", "symbol": "libtmux.constants.ResizeAdjustmentDirection.Up", @@ -5495,6 +5831,34 @@ "title": "Ownership and cleanup", "section": "topics" }, + { + "port": "py", + "symbol": "libtmux.pane.Pane.capture_pane", + "page": "/py/latest/guides/capturing-output/", + "title": "Capturing output", + "section": "guides" + }, + { + "port": "py", + "symbol": "libtmux.pane.Pane.capture_pane", + "page": "/py/latest/guides/getting-started/", + "title": "Getting started", + "section": "guides" + }, + { + "port": "py", + "symbol": "libtmux.pane.Pane.capture_pane", + "page": "/py/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, + { + "port": "py", + "symbol": "libtmux.pane.Pane.enter", + "page": "/py/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "py", "symbol": "libtmux.pane.Pane.from_env", @@ -5509,6 +5873,13 @@ "title": "Filtering and queries", "section": "concepts" }, + { + "port": "py", + "symbol": "libtmux.pane.Pane.send_keys", + "page": "/py/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "py", "symbol": "libtmux.pane.Pane.send_keys", @@ -5589,7 +5960,7 @@ { "port": "py", "symbol": "libtmux.pytest_plugin.session_params", - "page": "/guides/testing-with-libtmux/", + "page": "/py/latest/guides/testing-with-libtmux/", "title": "Testing with libtmux", "section": "guides" }, @@ -6307,13 +6678,6 @@ "title": "Rust workspace builder API", "section": "ports" }, - { - "port": "rs", - "symbol": "hooks.SparseValues.iter", - "page": "/guides/querying-and-filtering/", - "title": "Filtering and querying, in practice", - "section": "guides" - }, { "port": "rs", "symbol": "mcp.policy.Builder.selection", @@ -6384,6 +6748,27 @@ "title": "Options and hooks", "section": "topics" }, + { + "port": "rs", + "symbol": "pane.Pane.capture", + "page": "/rs/latest/guides/capturing-output/", + "title": "Capturing output", + "section": "guides" + }, + { + "port": "rs", + "symbol": "pane.Pane.capture", + "page": "/rs/latest/guides/getting-started/", + "title": "Getting started", + "section": "guides" + }, + { + "port": "rs", + "symbol": "pane.Pane.capture", + "page": "/rs/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "rs", "symbol": "pane.Pane.from_env", @@ -6412,6 +6797,13 @@ "title": "Options and hooks", "section": "topics" }, + { + "port": "rs", + "symbol": "pane.Pane.send_key_names", + "page": "/rs/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "rs", "symbol": "pane.Pane.send_key_names", @@ -6419,6 +6811,13 @@ "title": "Pane interaction", "section": "topics" }, + { + "port": "rs", + "symbol": "pane.Pane.send_keys", + "page": "/rs/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "rs", "symbol": "pane.Pane.send_keys", @@ -6426,6 +6825,13 @@ "title": "Pane interaction", "section": "topics" }, + { + "port": "rs", + "symbol": "pane.Pane.send_line", + "page": "/rs/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "rs", "symbol": "pane.Pane.send_line", @@ -6468,13 +6874,6 @@ "title": "Options and hooks", "section": "topics" }, - { - "port": "rs", - "symbol": "pane.Pane.wait_for_text", - "page": "/guides/testing-with-libtmux/", - "title": "Testing with libtmux", - "section": "guides" - }, { "port": "rs", "symbol": "pane.Pane.window", @@ -6517,6 +6916,13 @@ "title": "Attaching to tmux", "section": "guides" }, + { + "port": "rs", + "symbol": "server.Server", + "page": "/rs/latest/guides/testing-with-libtmux/", + "title": "Testing with libtmux", + "section": "guides" + }, { "port": "rs", "symbol": "server.Server", @@ -6559,6 +6965,13 @@ "title": "Traversal", "section": "topics" }, + { + "port": "rs", + "symbol": "server.Server.builder", + "page": "/rs/latest/guides/testing-with-libtmux/", + "title": "Testing with libtmux", + "section": "guides" + }, { "port": "rs", "symbol": "server.Server.builder", @@ -6608,6 +7021,13 @@ "title": "Socket and servers", "section": "topics" }, + { + "port": "rs", + "symbol": "server.Server.sessions", + "page": "/rs/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", + "section": "guides" + }, { "port": "rs", "symbol": "server.Server.sessions", @@ -6678,6 +7098,13 @@ "title": "Environment", "section": "topics" }, + { + "port": "rs", + "symbol": "session.Session.name", + "page": "/rs/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", + "section": "guides" + }, { "port": "rs", "symbol": "session.Session.panes", @@ -6807,24 +7234,17 @@ { "port": "rs", "symbol": "test.retry_until", - "page": "/guides/sending-keys/", - "title": "Sending keys", - "section": "guides" + "page": "/topics/waiting-and-retry/", + "title": "Waiting and retrying", + "section": "topics" }, { "port": "rs", - "symbol": "test.retry_until", - "page": "/guides/testing-with-libtmux/", + "symbol": "test.TestServer", + "page": "/rs/latest/guides/testing-with-libtmux/", "title": "Testing with libtmux", "section": "guides" }, - { - "port": "rs", - "symbol": "test.retry_until", - "page": "/topics/waiting-and-retry/", - "title": "Waiting and retrying", - "section": "topics" - }, { "port": "rs", "symbol": "test.TestServer", @@ -7882,6 +8302,13 @@ "title": "Attaching to tmux", "section": "guides" }, + { + "port": "swift", + "symbol": "Server", + "page": "/swift/latest/guides/getting-started/", + "title": "Getting started", + "section": "guides" + }, { "port": "swift", "symbol": "Server", @@ -7948,8 +8375,8 @@ { "port": "swift", "symbol": "Server.hasSession(_:)", - "page": "/guides/querying-and-filtering/", - "title": "Filtering and querying, in practice", + "page": "/swift/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", "section": "guides" }, { @@ -8001,6 +8428,13 @@ "title": "Environment", "section": "topics" }, + { + "port": "swift", + "symbol": "Server.run(_:in:)", + "page": "/swift/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "swift", "symbol": "Server.runHook(_:in:)", @@ -8330,6 +8764,13 @@ "title": "Use the Swift workspace builder", "section": "ports" }, + { + "port": "swift", + "symbol": "withTmuxServer(socketFileName:_:)", + "page": "/swift/latest/guides/testing-with-libtmux/", + "title": "Testing with libtmux", + "section": "guides" + }, { "port": "swift", "symbol": "Workspace", @@ -8729,6 +9170,27 @@ "title": "Architecture", "section": "topics" }, + { + "port": "ts", + "symbol": "pane.Pane.capture", + "page": "/ts/latest/guides/capturing-output/", + "title": "Capturing output", + "section": "guides" + }, + { + "port": "ts", + "symbol": "pane.Pane.capture", + "page": "/ts/latest/guides/getting-started/", + "title": "Getting started", + "section": "guides" + }, + { + "port": "ts", + "symbol": "pane.Pane.capture", + "page": "/ts/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "ts", "symbol": "pane.Pane.sendKeys", @@ -8743,6 +9205,13 @@ "title": "Pane interaction", "section": "topics" }, + { + "port": "ts", + "symbol": "pane.Pane.sendKeys", + "page": "/ts/latest/guides/sending-keys/", + "title": "Sending keys", + "section": "guides" + }, { "port": "ts", "symbol": "pane.Pane.setHook", @@ -8876,6 +9345,13 @@ "title": "Filtering and queries", "section": "concepts" }, + { + "port": "ts", + "symbol": "selection.Selection.one", + "page": "/ts/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", + "section": "guides" + }, { "port": "ts", "symbol": "selection.Selection.oneOrUndefined", @@ -8904,13 +9380,6 @@ "title": "Filtering and queries", "section": "concepts" }, - { - "port": "ts", - "symbol": "server.Server", - "page": "/guides/testing-with-libtmux/", - "title": "Testing with libtmux", - "section": "guides" - }, { "port": "ts", "symbol": "server.Server", @@ -8925,6 +9394,13 @@ "title": "Attaching to tmux", "section": "guides" }, + { + "port": "ts", + "symbol": "server.Server", + "page": "/ts/latest/guides/testing-with-libtmux/", + "title": "Testing with libtmux", + "section": "guides" + }, { "port": "ts", "symbol": "server.Server", @@ -9065,6 +9541,13 @@ "title": "Filtering and queries", "section": "concepts" }, + { + "port": "ts", + "symbol": "server.Server.snapshot", + "page": "/ts/latest/guides/querying-and-filtering/", + "title": "Querying and filtering", + "section": "guides" + }, { "port": "ts", "symbol": "server.Server.unsetEnvironment", @@ -9921,27 +10404,6 @@ "line": 152, "why": "not defined in the stated port" }, - { - "port": "dotnet", - "text": "IEnumerable.Matching(expression)", - "page": "/guides/querying-and-filtering/", - "line": 61, - "why": "not defined in the stated port" - }, - { - "port": "dotnet", - "text": "src/LibTmux.Testing/", - "page": "/guides/testing-with-libtmux/", - "line": 113, - "why": "not defined in the stated port" - }, - { - "port": "swift", - "text": "TmuxFixture", - "page": "/guides/testing-with-libtmux/", - "line": 150, - "why": "not defined in the stated port" - }, { "port": "kotlin", "text": "CardinalityException.MultipleMatches", diff --git a/site/test/complete-examples.test.ts b/site/test/complete-examples.test.ts index 4d7e9511..5c373e08 100644 --- a/site/test/complete-examples.test.ts +++ b/site/test/complete-examples.test.ts @@ -8,6 +8,7 @@ import attach from './fixtures/attach-examples.json' import products from './fixtures/product-examples.json' import queries from './fixtures/query-examples.json' import concepts from './fixtures/concept-examples.json' +import guides from './fixtures/guide-examples.json' import { remarkPortCode, resolvePortCode } from '../src/plugins/remark-port-code.mjs' import { rehypeCodeTabs } from '../src/plugins/rehype-code-tabs.mjs' import { docsEntryAvailable, pagePortLinks } from '../src/lib/page-port-links' @@ -19,7 +20,8 @@ const bodyOf = (page: string) => parsePage(page).content const fences = (markdown: string) => [...markdown.matchAll(/^```(\S+)([^\n]*)\n([\s\S]*?)^```/gm)] .map((match) => ({ language: match[1], title: /title="([^"]+)"/.exec(match[2])?.[1], code: match[3] })) const sha256 = (code: string) => createHash('sha256').update(code).digest('hex') -const examples = [...receipt.examples, ...attach.examples, ...products.examples, ...queries.examples, ...concepts.examples] +const examples = [...receipt.examples, ...attach.examples, ...products.examples, ...queries.examples, + ...concepts.examples, ...guides.examples] afterEach(() => vi.unstubAllEnvs()) @@ -66,7 +68,7 @@ describe('verified complete programs', () => { it.each(examples)('preserves $page in its root-mounted and native HTML/Markdown', async (example) => { const { content: body, frontmatter } = parsePage(example.page) - for (const port of ['', example.port]) { + for (const port of 'rootOnly' in example ? [''] : ['', example.port]) { vi.stubEnv('LIBTMUX_DOCS_PORT', port) const renderer = await createMarkdownProcessor({ remarkPlugins: [remarkPortCode], rehypePlugins: [rehypeCodeTabs], syntaxHighlight: false, @@ -91,6 +93,27 @@ describe('verified complete programs', () => { } }) + it.each(guides.examples)('keeps $page shell-only with working port routes to complete programs', (example) => { + const { content, frontmatter } = parsePage(example.page) + expect(frontmatter.supportedPorts).toEqual([]) + expect(fences(content).every((block) => ['sh', 'console'].includes(block.language))).toBe(true) + const ports = ['py', 'ts', 'go', 'rs', 'java', 'dotnet', 'cxx', 'swift'] + const variants = ports.map((port) => { + const id = `ports/${port}/${example.page}` + const variant = parsePage(id) + expect(variant.frontmatter.port).toBe(port) + expect(variant.frontmatter.route).toBe(example.page) + expect(variant.content).toMatch(/\]\((?:\.\.\/)+examples\/capture-pane-output\/\)|\]\(\.\.\/attaching-to-tmux\/\)/) + return { id, data: { port, route: example.page } } + }) + const docs = [{ id: example.page, data: { supportedPorts: [] as string[] } }, ...variants] + for (const port of ports) { + expect(docs.filter((doc) => docsEntryAvailable(doc, port))).toEqual([variants.find((doc) => doc.data.port === port)]) + } + const links = pagePortLinks({ pagePath: example.page, version: 'latest', defaults: {}, docs }) + expect(links.filter((link) => link.links.length).map((link) => link.port).sort()).toEqual(ports.sort()) + }) + it.each([receipt, attach])('keeps $page about tmux and routes each program to its port', async (receipt) => { const root = readPage(receipt.page) expect(root).toMatch(/^supportedPorts: \[\]$/m) diff --git a/site/test/fixtures/guide-examples.json b/site/test/fixtures/guide-examples.json new file mode 100644 index 00000000..9ed28346 --- /dev/null +++ b/site/test/fixtures/guide-examples.json @@ -0,0 +1,110 @@ +{ + "examples": [ + { + "port": "tmux", + "rootOnly": true, + "page": "guides/getting-started", + "sourceRevision": "94796f6b1182507efac8a272fc309a79e22e58a5", + "sourceUse": "tmux manual; native runtime versions recorded separately", + "verifiedTmuxVersions": [ + "3.2a", + "3.7c" + ], + "files": [ + { + "name": "start.sh", + "sha256": "c8463712889cd42eb7ef9edfe1c240b52ddea521062579b9e545c76125949b40" + } + ], + "shellRecipe": [ + "tmux -V", + "sh start.sh" + ], + "expectedOutput": "work:main" + }, + { + "port": "tmux", + "rootOnly": true, + "page": "guides/sending-keys", + "sourceRevision": "94796f6b1182507efac8a272fc309a79e22e58a5", + "sourceUse": "tmux manual; native runtime versions recorded separately", + "verifiedTmuxVersions": [ + "3.2a", + "3.7c" + ], + "files": [ + { + "name": "send.sh", + "sha256": "babbabad661b7635de7047476031d9bb40c49ae8811d5845d1848d51282c000a" + } + ], + "shellRecipe": [ + "sh send.sh" + ], + "expectedOutput": "Enter" + }, + { + "port": "tmux", + "rootOnly": true, + "page": "guides/capturing-output", + "sourceRevision": "94796f6b1182507efac8a272fc309a79e22e58a5", + "sourceUse": "tmux manual; native runtime versions recorded separately", + "verifiedTmuxVersions": [ + "3.2a", + "3.7c" + ], + "files": [ + { + "name": "history.sh", + "sha256": "e328f0ec37798b1519c10d2e5d6009ce0c6904a28bec1158be52bc9c056de0bb" + } + ], + "shellRecipe": [ + "sh history.sh" + ], + "expectedOutput": "row-1" + }, + { + "port": "tmux", + "rootOnly": true, + "page": "guides/querying-and-filtering", + "sourceRevision": "94796f6b1182507efac8a272fc309a79e22e58a5", + "sourceUse": "tmux manual; native runtime versions recorded separately", + "verifiedTmuxVersions": [ + "3.2a", + "3.7c" + ], + "files": [ + { + "name": "query.sh", + "sha256": "fecff77e546ba3309d87f450e2e2ac0a6cc65ebc23da0149d7d4b33a978c5c0a" + } + ], + "shellRecipe": [ + "sh query.sh" + ], + "expectedOutput": "work" + }, + { + "port": "tmux", + "rootOnly": true, + "page": "guides/testing-with-libtmux", + "sourceRevision": "94796f6b1182507efac8a272fc309a79e22e58a5", + "sourceUse": "tmux manual; native runtime versions recorded separately", + "verifiedTmuxVersions": [ + "3.2a", + "3.7c" + ], + "files": [ + { + "name": "test-tmux.sh", + "sha256": "37ee90390d88df2910b62e20e79660fa954b7bda0a1b45c5563a29b4de2f38e8" + } + ], + "shellRecipe": [ + "sh test-tmux.sh" + ], + "expectedOutput": "tmux fixture passed" + } + ] +}