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" + } + ] +}