diff --git a/WRITING.md b/WRITING.md index 2a13cd89..f7940234 100644 --- a/WRITING.md +++ b/WRITING.md @@ -108,7 +108,8 @@ the copied example. Preserve collected examples when changing formatting. Repeat a complete capture example with its native tools and tmux on `PATH`: ```console -$ python3 scripts/check-capture-prose.py \ +$ python3 scripts/check-example-prose.py \ + --example capture \ --port kotlin \ --output-dir /tmp/capture-kotlin-proof ``` @@ -117,10 +118,12 @@ The output directory must be new. The runner extracts the displayed files, checks their recorded hashes, executes the displayed setup, and saves logs and a result. Dependency downloads and native builds are separate from the routine site tests. A changed hash needs a new native run before review. +Use `--example attach` to check the existing-server programs in the attach guide. Put explanatory comments on separate lines above the code they describe. -Wrap example comments at 80 columns, including indentation. Put long source -links and attribution in prose outside the code block. +Limit example comments to 100 columns, including indentation; prefer shorter +lines that fit the code panel. Put long source links and attribution in prose +outside the code block. ### Examples across ports diff --git a/scripts/check-capture-prose.py b/scripts/check-example-prose.py similarity index 86% rename from scripts/check-capture-prose.py rename to scripts/check-example-prose.py index 450b2f3e..c63c8127 100644 --- a/scripts/check-capture-prose.py +++ b/scripts/check-example-prose.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Run a complete capture example exactly as displayed, including its setup. +"""Run a complete example exactly as displayed, including its setup. Use --port to choose a language and --output-dir for a new evidence directory. The selected language's native tools, Git, and tmux must already be on PATH. @@ -18,12 +18,15 @@ def main(): repo = Path(__file__).resolve().parent.parent - manifest = json.loads((repo / 'site/test/fixtures/capture-examples.json').read_text()) - examples = {item['port']: item for item in manifest['examples']} parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--port', required=True, choices=examples) + parser.add_argument('--example', choices=['capture', 'attach'], default='capture') + parser.add_argument('--port', required=True) 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['port']: item for item in manifest['examples']} + if args.port not in examples: + parser.error(f'Unknown port: {args.port}; choose from {", ".join(examples)}') example = examples[args.port] page = repo / 'site/src/content/docs' / (example['page'] + '.md') content = page.read_text() @@ -68,7 +71,7 @@ def main(): if result.returncode: break passed = all(row['exit'] == 0 for row in results) - passed = passed and 'libtmux capture ready' in log.read_text().splitlines() + passed = passed and example.get('expectedOutput', 'libtmux capture ready') in log.read_text().splitlines() report = {'port': args.port, 'sourceRevision': example['sourceRevision'], 'pageSha256': hashlib.sha256(page.read_bytes()).hexdigest(), 'files': example['files'], 'runs': results, 'passed': passed, diff --git a/site/src/content/docs/guides/attaching-to-tmux.md b/site/src/content/docs/guides/attaching-to-tmux.md index e3e3d53e..46077014 100644 --- a/site/src/content/docs/guides/attaching-to-tmux.md +++ b/site/src/content/docs/guides/attaching-to-tmux.md @@ -1,7 +1,7 @@ --- -supportedPorts: [py, ts, rs, go, java, dotnet, cxx, swift] +supportedPorts: [] title: Attaching to tmux -description: What a plain constructor call actually connects to, and how to find a session that might already exist instead of always creating a new one. +description: Create or attach to a tmux session, select its socket, and keep terminal attachment separate from automation. sidebar: label: Attaching to tmux group: Guides @@ -9,160 +9,136 @@ sidebar: tableOfContents: true --- -Obtain a server and session handle to control tmux from your program. Your -process keeps its own stdin and stdout, and tmux continues running -independently. [Attach and send keys](/examples/attach-and-send-keys/) -demonstrates this workflow. - - -Attaching your terminal is a separate operation. Python's `Session.attach()` -runs `tmux attach-session` and hands the terminal to tmux. -[tmuxp](https://tmuxp.git-pull.com/) uses it after building a workspace. Check -your port's reference if your program needs to hand over the terminal. - - -## Which socket a bare constructor reaches - -Use an explicit socket when several tmux servers may be running. The examples -below show each constructor's defaults and environment-aware alternatives. To -locate the server from inside a pane, use the port's environment lookup API for -`TMUX` and `TMUX_PANE`. - -```python -# Server() with no arguments talks to tmux's own default socket. -# Server(socket_name=...) or Server(socket_path=...) pick a different one. -server = libtmux.Server() - -# from_env() is also on Session, Window, and Pane, for code running inside a -# pane that wants to ask "where am I" instead of being told. -server = libtmux.Server.from_env() -``` +Attaching opens a tmux session in your terminal. Its shells and programs keep +running when you detach. A script can also query or control that session +without taking over a terminal. -```typescript -// Select the default tmux socket. -const server = new Server(); -``` +## Open a session in your terminal -```go -// Resolves the tmux binary once through the snapshotted PATH and freezes -// it. SocketName and SocketPath select a socket explicitly (SocketPath wins -// if both are set); a Server built this way does not drift if the -// environment changes later. -server, err := tmux.NewServer(tmux.ServerOptions{}) -if err != nil { - return err -} -``` +Run this from a terminal outside tmux. It creates `work` if needed and attaches +to it otherwise. `-L libtmux-demo` keeps this demonstration on its own named +server. `-f /dev/null` skips personal tmux configuration for this demonstration. -```rust -// Server::new() uses the process's own environment. from_env() reads the -// TMUX variable directly; find.rs tries the pane-local one first and falls -// back to a fresh connection. -let server = Server::from_env().or_else(|_| Server::new())?; +```console +$ tmux -L libtmux-demo -f /dev/null new-session -A -s work ``` -```java -Server server = Server.open( - ServerConfig.builder().endpoint(ServerEndpoint.socketPath(socket)).build()); +Detach with **Ctrl-b**, then **d**. You return to the original shell while +the tmux session keeps running. List that server's sessions: -// The pane-local read-back: takes nothing, returns empty outside a pane. -TmuxEnvironment.current(); +```console +$ tmux -L libtmux-demo list-sessions ``` -```csharp -// Resolves in a fixed order: an explicit ServerConnectionOptions, then -// LIBTMUX_SOCKET_PATH, then LIBTMUX_SOCKET_NAME (under TMUX_TMPDIR, or -// /tmp), then the socket named "default". A named option always wins over -// an environment variable. -Server server = await Server.ConnectAsync(); - -// The separate pane-local read-back; ConnectAsync never consults TMUX. -Server fromPane = Server.FromEnvironment(); -``` - -```cpp -// Four named constructors instead of one flexible one: pick the one that -// names how you're reaching this tmux: -libtmux::Server::from_env(); // inside tmux -libtmux::Server::at_socket_name(name); -libtmux::Server::at_socket_path(path); -libtmux::Server::at_default(); // "my tmux", to a person -``` +Attach to the existing session again. The leading `=` selects its exact name: -```swift -// Select the default tmux socket explicitly. -let server = try Server(socketName: "default") +```console +$ tmux -L libtmux-demo attach-session -t '=work' ``` -[Socket and servers](/topics/socket-and-servers/) covers endpoint selection -and liveness. [Environment](/topics/environment/) covers pane-local lookup. +`attach-session` expects a session to exist. `new-session -A` is the command +to use when either creating or attaching is acceptable. From inside tmux, +`attach-session` switches the attached client to the target session. -## Finding a session instead of always creating one +After detaching, remove the demonstration session when you are finished: -A script that runs more than once usually wants "attach if a session by -this name already exists, create it otherwise," not a fresh session every -time. - -```python -# default only stands in for *absence*: an ambiguous match still raises -# MultipleObjectsReturned even with a default supplied: handing back an -# arbitrary match from several is how a script ends up driving the wrong -# pane. See Filtering and queries. -session = server.sessions.get(session_name="demo", default=None) -if session is None: - session = server.new_session(session_name="demo") +```console +$ tmux -L libtmux-demo kill-session -t '=work' ``` -```typescript -const session = snapshot.sessions.where({ name: "demo" }).oneOrUndefined(); + + +## Choose the server socket + +A socket identifies a tmux server. Use the same selection on every command: + +- `-L name` selects a named socket in tmux's socket directory. +- `-S path` selects an explicit socket path and overrides `-L`. +- Without either flag, tmux uses the socket from `TMUX` when applicable, + otherwise its default socket. + +Inside a pane, `TMUX` identifies its server and `TMUX_PANE` identifies the +pane. Prefer explicit socket selection in automation that may run both +inside and outside tmux. + +## Query a session from a shell script + +This complete program creates an isolated server, looks up `work`, and prints +its name. Each command stays in the calling shell; no terminal is attached. +It stops its own server on success or failure. + +Save it as `connect.sh` and run `sh connect.sh`, or paste the whole block into +a POSIX shell. It requires tmux 3.2a or newer. + +```sh title="connect.sh" +( + set -eu + directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-attach.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" || exit 1 + exit "$status" + } + trap cleanup 0 + trap 'exit 1' HUP INT TERM + + unset TMUX TMUX_PANE + tmux -S "$socket" -f /dev/null new-session -d -s work /bin/cat + tmux -S "$socket" has-session -t '=work' + tmux -S "$socket" list-sessions -F '#{session_name}' +) ``` -```go -// Push the check into tmux itself with a typed filter, rather than reading -// everything back and filtering in the process. -live := tmux.TmuxFilter("#{==:#{session_name},demo}") -sessions, err := server.SearchSessions(ctx, &live) -if err != nil { - return err -} -fmt.Println("matching sessions:", len(sessions)) -``` +`-d` starts the session without attaching. `/bin/cat` keeps its pane open +without loading a shell configuration. The script addresses only its private +socket. If shutdown fails, it reports the error and keeps that socket's +directory for inspection. -```java -Session session = server.hasSession("work") - ? server.sessions().stream() - .filter(candidate -> candidate.name().equals("work")) - .findFirst() - .orElseThrow() - : server.newSession("work"); -``` + -```swift -// hasSession answers the question directly as a Bool, no exception needed -// either way. -if try await server.hasSession("work") == false { - _ = try await server.newSession(named: "work", windowName: "start") -} -``` +## Find a session before creating one - -`Server.HasSessionAsync(name)` checks for a session. Use `NewSessionRequest.ReplaceExisting` -with `Server.CreateSessionAsync` only when killing and recreating it is intended. - +Use `has-session -t '=name'` to check an exact session name. It exits +unsuccessfully when tmux cannot find the session or contact the server; keep +the diagnostic so you can distinguish those failures. -Finding an object and creating one are separate operations. Another client -can change tmux state between them. Handle the creation error if the name was -taken after the lookup. +A lookup does not reserve a name. Another client can create or remove a +session before your next command. Handle the result of `new-session` even +after checking. Use `new-session -A` for the interactive create-or-attach +workflow shown above. -[Filtering and querying](../querying-and-filtering/) covers absent and -ambiguous matches. [Attach and send keys](/examples/attach-and-send-keys/) -contains complete programs and their source details. +## Connect from a language library + +Use the port menu to open this guide with a complete program, imports, build +files, and a private-server launcher. Each program connects to an existing +socket and leaves that server running: + +[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/) ## Where to go next -- [Sending keys](../sending-keys/) and [Capturing output](../capturing-output/) - pick up once you have a pane handle. -- [Attach and send keys](/examples/attach-and-send-keys/) has the full, - sourced code for the round trip this guide assumes. -- [Testing with libtmux](../testing-with-libtmux/) if the server you want to - attach to is one your own test suite should own and tear down. +- [Sending keys](../sending-keys/) explains input and command completion. +- [Capturing output](../capturing-output/) reads a pane's screen and history. +- [Socket and servers](/topics/socket-and-servers/) covers server selection. + +The [tmux manual source](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1) +describes `new-session`, `attach-session`, socket selection, and targeting. diff --git a/site/src/content/docs/ports/cxx/examples/capture-pane-output.md b/site/src/content/docs/ports/cxx/examples/capture-pane-output.md index ec60ae7b..75d0ba8c 100644 --- a/site/src/content/docs/ports/cxx/examples/capture-pane-output.md +++ b/site/src/content/docs/ports/cxx/examples/capture-pane-output.md @@ -18,6 +18,10 @@ This complete program creates a private tmux server, captures its output, and cleans up. Follow the [setup and run instructions](#setup-and-run) below. You need tmux and a Unix environment; no existing tmux session is required. +`ScopedTmuxServer` is temporary example scaffolding. In an application, use a +[`Server`](../../reference/libtmux-server/) connected to the tmux server you manage. +Future versions of this example will use that regular server object directly. + ## Read what's on screen The program sends `printf` with a leading newline, then waits for the complete diff --git a/site/src/content/docs/ports/cxx/guides/attaching-to-tmux.md b/site/src/content/docs/ports/cxx/guides/attaching-to-tmux.md new file mode 100644 index 00000000..69c9caf7 --- /dev/null +++ b/site/src/content/docs/ports/cxx/guides/attaching-to-tmux.md @@ -0,0 +1,140 @@ +--- +port: cxx +route: guides/attaching-to-tmux +title: Attaching to tmux +description: Connect to an existing tmux server and find a session with C++. +sidebar: + label: Attaching to tmux + group: Guides + order: 3 +tableOfContents: true +--- + +Connect a `Server` to an explicit socket and find the existing `work` session. +The program prints its name and leaves the tmux server running. It reports an +error if the connection fails or the session is absent. + +This controls tmux from your program. To open a session in your terminal, use +`tmux attach-session`; the [shared guide](../../../../guides/attaching-to-tmux/) covers +interactive attachment and detaching. + + + +## Connect to an existing server + +Save the complete program as `connect.cpp`. `LIBTMUX_SOCKET_PATH` selects +the existing server. The launcher below supplies a private socket for trying +the example. + +```cpp title="connect.cpp" +#include +#include +#include + +#include + +int main() { + try { + const char* socket = std::getenv("LIBTMUX_SOCKET_PATH"); + if (!socket) throw std::runtime_error("Set LIBTMUX_SOCKET_PATH to an existing socket"); + auto server = libtmux::Server::at_socket_path(socket); + if (!server) throw std::runtime_error(server.error().diagnostic); + auto sessions = server->sessions(); + if (!sessions) throw std::runtime_error(sessions.error().diagnostic); + for (const auto& session : *sessions) { + if (session.name() == "work") { + std::cout << "work\n"; + return 0; + } + } + throw std::runtime_error("The work session does not exist"); + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} +``` + +## Setup and run + +Use an empty directory on Linux with Git and tmux 3.2a or newer installed. + +This example was checked with Clang 18, libc++ 18, CMake 3.25+. + +Use CMake 3.25 or newer, Ninja, and Clang 18 with libc++ 18. + +Save this file beside the program using the displayed filename. + +```cmake title="CMakeLists.txt" +cmake_minimum_required(VERSION 3.25) +project(connect_example LANGUAGES CXX) +set(LIBTMUX_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(LIBTMUX_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +add_subdirectory(libtmux-source) +add_executable(connect connect.cpp) +target_compile_features(connect PRIVATE cxx_std_23) +target_link_libraries(connect PRIVATE libtmux::libtmux) +``` + +Save the launcher as `run.sh`. It starts an isolated tmux server, runs the +program, checks that the session still exists, then stops only that server. +Cleanup runs after failures too. A failed shutdown keeps its socket directory +and prints its location for inspection. + +```sh title="run.sh" +#!/bin/sh +set -eu + +binary=$(command -v tmux) +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-cxx-attach.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || exit 1 + exit "$status" +} +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +unset TMUX TMUX_PANE +export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" +"$binary" -S "$socket" -f /dev/null new-session -d -s work /bin/cat +"$@" +"$binary" -S "$socket" has-session -t '=work' +``` + +Fetch the verified library revision, build, and run: + +```console +$ git clone https://github.com/libtmux/libtmux-cxx libtmux-source && + git -C libtmux-source checkout 393d4b0ad666f18a6581f1eb281741a75a7503f0 && + cmake -S . -B build -G Ninja \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DCMAKE_CXX_FLAGS=-stdlib=libc++ \ + -DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++ && + cmake --build build --target connect --parallel 2 && + sh run.sh ./build/connect +``` + +The program prints `work`. To use an existing server of your own, set +`LIBTMUX_SOCKET_PATH` to its socket and run the program without the launcher. +That launcher is responsible for the demonstration server's lifetime. + + + +## Find or create a session + +The example only looks up a session. If your application creates a session +after an unsuccessful lookup, another client may create the same name between +those operations. Handle the creation error instead of assuming the lookup +reserves the name. + +For a complete program that starts and owns its server, see +[Capture pane output](/examples/capture-pane-output/). Continue with +[Sending keys](/guides/sending-keys/) once you have a pane handle. diff --git a/site/src/content/docs/ports/cxx/workspace/internals/examples.md b/site/src/content/docs/ports/cxx/workspace/internals/examples.md index 8ee1cc5f..cfa208c6 100644 --- a/site/src/content/docs/ports/cxx/workspace/internals/examples.md +++ b/site/src/content/docs/ports/cxx/workspace/internals/examples.md @@ -32,6 +32,10 @@ Save the following build file. The typed builder is a source header; this exampl does not use the optional YAML reader. The public testing library supplies private-server startup and cleanup. +`ScopedTmuxServer` is temporary example scaffolding. In an application, use a +[`Server`](../../../reference/libtmux-server/) connected to the tmux server you manage. +Future versions of this example will use that regular server object directly. + ```cmake title="CMakeLists.txt" cmake_minimum_required(VERSION 3.25) project(workspace_example LANGUAGES CXX) diff --git a/site/src/content/docs/ports/dotnet/guides/attaching-to-tmux.md b/site/src/content/docs/ports/dotnet/guides/attaching-to-tmux.md new file mode 100644 index 00000000..749819c9 --- /dev/null +++ b/site/src/content/docs/ports/dotnet/guides/attaching-to-tmux.md @@ -0,0 +1,126 @@ +--- +port: dotnet +route: guides/attaching-to-tmux +title: Attaching to tmux +description: Connect to an existing tmux server and find a session with .NET. +sidebar: + label: Attaching to tmux + group: Guides + order: 3 +tableOfContents: true +--- + +Connect a `Server` to an explicit socket and find the existing `work` session. +The program prints its name and leaves the tmux server running. It reports an +error if the connection fails or the session is absent. + +This controls tmux from your program. To open a session in your terminal, use +`tmux attach-session`; the [shared guide](../../../../guides/attaching-to-tmux/) covers +interactive attachment and detaching. + + + +## Connect to an existing server + +Save the complete program as `Program.cs`. `LIBTMUX_SOCKET_PATH` selects +the existing server. The launcher below supplies a private socket for trying +the example. + +```csharp title="Program.cs" +using System; +using System.Threading; +using System.Threading.Tasks; +using LibTmux; + +string socket = Environment.GetEnvironmentVariable("LIBTMUX_SOCKET_PATH") + ?? throw new InvalidOperationException("Set LIBTMUX_SOCKET_PATH to an existing socket"); +using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); +Server server = await Server.ConnectAsync( + new ServerConnectionOptions { SocketPath = socket }, timeout.Token); +if (!await server.HasSessionAsync("work", cancellationToken: timeout.Token)) + throw new InvalidOperationException("The work session does not exist"); +Console.WriteLine("work"); +``` + +## Setup and run + +Use an empty directory on Linux with Git and tmux 3.2a or newer installed. + +This example was checked with .NET SDK 10.0.302. + +Save this file beside the program using the displayed filename. + +```xml title="Connect.csproj" + + + Exe + net10.0 + disable + enable + false + + + + + + +``` + +Save the launcher as `run.sh`. It starts an isolated tmux server, runs the +program, checks that the session still exists, then stops only that server. +Cleanup runs after failures too. A failed shutdown keeps its socket directory +and prints its location for inspection. + +```sh title="run.sh" +#!/bin/sh +set -eu + +binary=$(command -v tmux) +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-dotnet-attach.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || exit 1 + exit "$status" +} +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +unset TMUX TMUX_PANE +export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" +"$binary" -S "$socket" -f /dev/null new-session -d -s work /bin/cat +"$@" +"$binary" -S "$socket" has-session -t '=work' +``` + +Fetch the verified library revision, build, and run: + +```console +$ git clone https://github.com/libtmux/libtmux-dotnet libtmux-source && + git -C libtmux-source checkout 320dc64f4b8b7815842471327a5e6b84a1499bf8 && + dotnet build Connect.csproj --maxcpucount:1 && + sh run.sh dotnet run --project Connect.csproj --no-build +``` + +The program prints `work`. To use an existing server of your own, set +`LIBTMUX_SOCKET_PATH` to its socket and run the program without the launcher. +That launcher is responsible for the demonstration server's lifetime. + + + +## Find or create a session + +The example only looks up a session. If your application creates a session +after an unsuccessful lookup, another client may create the same name between +those operations. Handle the creation error instead of assuming the lookup +reserves the name. + +For a complete program that starts and owns its server, see +[Capture pane output](/examples/capture-pane-output/). Continue with +[Sending keys](/guides/sending-keys/) once you have a pane handle. diff --git a/site/src/content/docs/ports/fsharp/guides/attaching-to-tmux.md b/site/src/content/docs/ports/fsharp/guides/attaching-to-tmux.md new file mode 100644 index 00000000..d3cb6038 --- /dev/null +++ b/site/src/content/docs/ports/fsharp/guides/attaching-to-tmux.md @@ -0,0 +1,136 @@ +--- +port: fsharp +route: guides/attaching-to-tmux +title: Attaching to tmux +description: Connect to an existing tmux server and find a session with F#. +sidebar: + label: Attaching to tmux + group: Guides + order: 3 +tableOfContents: true +--- + +Connect a `Server` to an explicit socket and find the existing `work` session. +The program prints its name and leaves the tmux server running. It reports an +error if the connection fails or the session is absent. + +This controls tmux from your program. To open a session in your terminal, use +`tmux attach-session`; the [shared guide](../../../../guides/attaching-to-tmux/) covers +interactive attachment and detaching. + + + +## Connect to an existing server + +Save the complete program as `Program.fs`. `LIBTMUX_SOCKET_PATH` selects +the existing server. The launcher below supplies a private socket for trying +the example. + +```fsharp title="Program.fs" +open System +open System.Threading +open System.Threading.Tasks +open LibTmux + +let connect () = task { + let socket = Environment.GetEnvironmentVariable("LIBTMUX_SOCKET_PATH") + if String.IsNullOrEmpty(socket) then + invalidOp "Set LIBTMUX_SOCKET_PATH to an existing socket" + use timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5.0)) + let! server = Server.ConnectAsync( + ServerConnectionOptions(SocketPath = socket), timeout.Token) + let! exists = server.HasSessionAsync("work", cancellationToken = timeout.Token) + if not exists then invalidOp "The work session does not exist" + printfn "work" +} + +[] +let main _ = + try + connect().GetAwaiter().GetResult() + 0 + with error -> + eprintfn "%O" error + 1 +``` + +## Setup and run + +Use an empty directory on Linux with Git and tmux 3.2a or newer installed. + +This example was checked with .NET SDK 10.0.302. + +Save this file beside the program using the displayed filename. + +```xml title="Connect.fsproj" + + + Exe + net10.0 + + + + + + +``` + +Save the launcher as `run.sh`. It starts an isolated tmux server, runs the +program, checks that the session still exists, then stops only that server. +Cleanup runs after failures too. A failed shutdown keeps its socket directory +and prints its location for inspection. + +```sh title="run.sh" +#!/bin/sh +set -eu + +binary=$(command -v tmux) +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-fsharp-attach.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || exit 1 + exit "$status" +} +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +unset TMUX TMUX_PANE +export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" +"$binary" -S "$socket" -f /dev/null new-session -d -s work /bin/cat +"$@" +"$binary" -S "$socket" has-session -t '=work' +``` + +Fetch the verified library revision, build, and run: + +```console +$ git clone https://github.com/libtmux/libtmux-dotnet libtmux-source && + git -C libtmux-source checkout 661287848a6cfb407f37114b25e8249a29f99e3f && + dotnet build Connect.fsproj --maxcpucount:1 \ + -p:DisableImplicitLibraryPacksFolder=true \ + -p:RestorePackagesPath="$PWD/.packages" && + sh run.sh dotnet run --project Connect.fsproj --no-build +``` + +The program prints `work`. To use an existing server of your own, set +`LIBTMUX_SOCKET_PATH` to its socket and run the program without the launcher. +That launcher is responsible for the demonstration server's lifetime. + + + +## Find or create a session + +The example only looks up a session. If your application creates a session +after an unsuccessful lookup, another client may create the same name between +those operations. Handle the creation error instead of assuming the lookup +reserves the name. + +For a complete program that starts and owns its server, see +[Capture pane output](/examples/capture-pane-output/). diff --git a/site/src/content/docs/ports/go/guides/attaching-to-tmux.md b/site/src/content/docs/ports/go/guides/attaching-to-tmux.md new file mode 100644 index 00000000..3099c552 --- /dev/null +++ b/site/src/content/docs/ports/go/guides/attaching-to-tmux.md @@ -0,0 +1,140 @@ +--- +port: go +route: guides/attaching-to-tmux +title: Attaching to tmux +description: Connect to an existing tmux server and find a session with Go. +sidebar: + label: Attaching to tmux + group: Guides + order: 3 +tableOfContents: true +--- + +Connect a `Server` to an explicit socket and find the existing `work` session. +The program prints its name and leaves the tmux server running. It reports an +error if the connection fails or the session is absent. + +This controls tmux from your program. To open a session in your terminal, use +`tmux attach-session`; the [shared guide](../../../../guides/attaching-to-tmux/) covers +interactive attachment and detaching. + + + +## Connect to an existing server + +Save the complete program as `main.go`. `LIBTMUX_SOCKET_PATH` selects +the existing server. The launcher below supplies a private socket for trying +the example. + +```go title="main.go" +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/libtmux/libtmux-go/tmux" +) + +func main() { + socket := os.Getenv("LIBTMUX_SOCKET_PATH") + if socket == "" { + log.Fatal("Set LIBTMUX_SOCKET_PATH to an existing socket") + } + server, err := tmux.NewServer(tmux.ServerOptions{SocketPath: socket}) + if err != nil { + log.Fatal(err) + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + filter := tmux.TmuxFilter("#{==:#{session_name},work}") + sessions, err := server.SearchSessions(ctx, &filter) + if err != nil { + log.Fatal(err) + } + if len(sessions) != 1 { + log.Fatalf("Expected one work session, found %d", len(sessions)) + } + fmt.Println("work") +} +``` + +## Setup and run + +Use an empty directory on Linux with Git and tmux 3.2a or newer installed. + +This example was checked with Go 1.26.8. + +Save this file beside the program using the displayed filename. + +```text title="go.mod" +module example.com/connect + +go 1.26.0 + +require github.com/libtmux/libtmux-go v0.0.0 + +replace github.com/libtmux/libtmux-go => ./libtmux +``` + +Save the launcher as `run.sh`. It starts an isolated tmux server, runs the +program, checks that the session still exists, then stops only that server. +Cleanup runs after failures too. A failed shutdown keeps its socket directory +and prints its location for inspection. + +```sh title="run.sh" +#!/bin/sh +set -eu + +binary=$(command -v tmux) +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-go-attach.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || exit 1 + exit "$status" +} +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +unset TMUX TMUX_PANE +export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" +"$binary" -S "$socket" -f /dev/null new-session -d -s work /bin/cat +"$@" +"$binary" -S "$socket" has-session -t '=work' +``` + +Fetch the verified library revision, build, and run: + +```console +$ git clone https://github.com/libtmux/libtmux-go libtmux && + git -C libtmux checkout bb06e26e116e941813ca40bf45e7e3a47d38f52a && + GOWORK=off go mod tidy && + sh run.sh env GOWORK=off go run . +``` + +The program prints `work`. To use an existing server of your own, set +`LIBTMUX_SOCKET_PATH` to its socket and run the program without the launcher. +That launcher is responsible for the demonstration server's lifetime. + + + +## Find or create a session + +The example only looks up a session. If your application creates a session +after an unsuccessful lookup, another client may create the same name between +those operations. Handle the creation error instead of assuming the lookup +reserves the name. + +For a complete program that starts and owns its server, see +[Capture pane output](/examples/capture-pane-output/). Continue with +[Sending keys](/guides/sending-keys/) once you have a pane handle. diff --git a/site/src/content/docs/ports/java/guides/attaching-to-tmux.md b/site/src/content/docs/ports/java/guides/attaching-to-tmux.md new file mode 100644 index 00000000..280152c9 --- /dev/null +++ b/site/src/content/docs/ports/java/guides/attaching-to-tmux.md @@ -0,0 +1,120 @@ +--- +port: java +route: guides/attaching-to-tmux +title: Attaching to tmux +description: Connect to an existing tmux server and find a session with Java. +sidebar: + label: Attaching to tmux + group: Guides + order: 3 +tableOfContents: true +--- + +Connect a `Server` to an explicit socket and find the existing `work` session. +The program prints its name and leaves the tmux server running. It reports an +error if the connection fails or the session is absent. + +This controls tmux from your program. To open a session in your terminal, use +`tmux attach-session`; the [shared guide](../../../../guides/attaching-to-tmux/) covers +interactive attachment and detaching. + + + +## Connect to an existing server + +Save the complete program as `Connect.java`. `LIBTMUX_SOCKET_PATH` selects +the existing server. The launcher below supplies a private socket for trying +the example. + +```java title="Connect.java" +import io.github.libtmux.Server; +import io.github.libtmux.ServerConfig; +import io.github.libtmux.ServerEndpoint; +import io.github.libtmux.Session; +import java.nio.file.Path; +import java.time.Duration; +import java.util.Objects; + +public final class Connect { + public static void main(String[] args) { + String socket = Objects.requireNonNull(System.getenv("LIBTMUX_SOCKET_PATH"), + "Set LIBTMUX_SOCKET_PATH to an existing socket"); + ServerConfig config = ServerConfig.builder() + .endpoint(ServerEndpoint.socketPath(Path.of(socket))) + .defaultTimeout(Duration.ofSeconds(5)) + .build(); + try (Server server = Server.open(config)) { + Session session = server.sessions().stream() + .filter(candidate -> candidate.name().equals("work")) + .findFirst() + .orElseThrow(() -> new IllegalStateException("The work session does not exist")); + System.out.println(session.name()); + } + } +} +``` + +## Setup and run + +Use an empty directory on Linux with Git and tmux 3.2a or newer installed. + +This example was checked with JDK 25.0.3, library bytecode target Java 21. + +Save the launcher as `run.sh`. It starts an isolated tmux server, runs the +program, checks that the session still exists, then stops only that server. +Cleanup runs after failures too. A failed shutdown keeps its socket directory +and prints its location for inspection. + +```sh title="run.sh" +#!/bin/sh +set -eu + +binary=$(command -v tmux) +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-java-attach.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || exit 1 + exit "$status" +} +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +unset TMUX TMUX_PANE +export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" +"$binary" -S "$socket" -f /dev/null new-session -d -s work /bin/cat +"$@" +"$binary" -S "$socket" has-session -t '=work' +``` + +Fetch the verified library revision, build, and run: + +```console +$ git clone https://github.com/libtmux/libtmux-java libtmux && + git -C libtmux checkout 842228310449e879ebcaa3f910597757c9dbffd6 && + (cd libtmux && ./gradlew :libtmux:jar) && + sh run.sh java --class-path 'libtmux/libtmux/build/libs/*' Connect.java +``` + +The program prints `work`. To use an existing server of your own, set +`LIBTMUX_SOCKET_PATH` to its socket and run the program without the launcher. +That launcher is responsible for the demonstration server's lifetime. + + + +## Find or create a session + +The example only looks up a session. If your application creates a session +after an unsuccessful lookup, another client may create the same name between +those operations. Handle the creation error instead of assuming the lookup +reserves the name. + +For a complete program that starts and owns its server, see +[Capture pane output](/examples/capture-pane-output/). Continue with +[Sending keys](/guides/sending-keys/) once you have a pane handle. diff --git a/site/src/content/docs/ports/kotlin/guides/attaching-to-tmux.md b/site/src/content/docs/ports/kotlin/guides/attaching-to-tmux.md new file mode 100644 index 00000000..547f315f --- /dev/null +++ b/site/src/content/docs/ports/kotlin/guides/attaching-to-tmux.md @@ -0,0 +1,148 @@ +--- +port: kotlin +route: guides/attaching-to-tmux +title: Attaching to tmux +description: Connect to an existing tmux server and find a session with Kotlin. +sidebar: + label: Attaching to tmux + group: Guides + order: 3 +tableOfContents: true +--- + +Connect a `Server` to an explicit socket and find the existing `work` session. +The program prints its name and leaves the tmux server running. It reports an +error if the connection fails or the session is absent. + +This controls tmux from your program. To open a session in your terminal, use +`tmux attach-session`; the [shared guide](../../../../guides/attaching-to-tmux/) covers +interactive attachment and detaching. + + + +## Connect to an existing server + +Save the complete program as `Connect.kt`. `LIBTMUX_SOCKET_PATH` selects +the existing server. The launcher below supplies a private socket for trying +the example. + +```kotlin title="Connect.kt" +import io.github.libtmux.ServerConfig +import io.github.libtmux.ServerEndpoint +import io.github.libtmux.kotlin.sessions +import io.github.libtmux.kotlin.withServer +import java.nio.file.Path +import java.time.Duration +import kotlinx.coroutines.runBlocking + +fun main() = runBlocking { + val socket = requireNotNull(System.getenv("LIBTMUX_SOCKET_PATH")) { + "Set LIBTMUX_SOCKET_PATH to an existing socket" + } + val config = ServerConfig.builder() + .endpoint(ServerEndpoint.socketPath(Path.of(socket))) + .defaultTimeout(Duration.ofSeconds(5)) + .build() + withServer(config) { server -> + val session = server.sessions().single { it.name == "work" } + println(session.name) + } +} +``` + +## Setup and run + +Use an empty directory on Linux with Git and tmux 3.2a or newer installed. + +This example was checked with JDK 25.0.3, Kotlin 2.4.10. + +Save this file beside the program using the displayed filename. + +```kotlin title="build.gradle.kts" +plugins { + application + kotlin("jvm") version "2.4.10" +} + +repositories { mavenCentral() } +dependencies { + implementation("io.github.libtmux:libtmux-kotlin:0.0.1-alpha.17-SNAPSHOT") +} +kotlin { + jvmToolchain(25) + sourceSets.main { kotlin.srcDir("."); kotlin.include("Connect.kt") } +} +application { mainClass.set("ConnectKt") } +``` + +Save this file beside the program using the displayed filename. + +```kotlin title="settings.gradle.kts" +rootProject.name = "connect" +includeBuild("libtmux-source") +``` + +Save this file beside the program using the displayed filename. + +```properties title="gradle.properties" +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 +kotlin.daemon.jvmargs=-Xmx2g +org.gradle.workers.max=2 +``` + +Save the launcher as `run.sh`. It starts an isolated tmux server, runs the +program, checks that the session still exists, then stops only that server. +Cleanup runs after failures too. A failed shutdown keeps its socket directory +and prints its location for inspection. + +```sh title="run.sh" +#!/bin/sh +set -eu + +binary=$(command -v tmux) +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-kotlin-attach.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || exit 1 + exit "$status" +} +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +unset TMUX TMUX_PANE +export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" +"$binary" -S "$socket" -f /dev/null new-session -d -s work /bin/cat +"$@" +"$binary" -S "$socket" has-session -t '=work' +``` + +Fetch the verified library revision, build, and run: + +```console +$ git clone https://github.com/libtmux/libtmux-java libtmux-source && + git -C libtmux-source checkout 85ebf6955e56703c5be74e2afd34a18309044741 && + sh run.sh ./libtmux-source/gradlew --project-dir . run --console=plain --max-workers=2 +``` + +The program prints `work`. To use an existing server of your own, set +`LIBTMUX_SOCKET_PATH` to its socket and run the program without the launcher. +That launcher is responsible for the demonstration server's lifetime. + + + +## Find or create a session + +The example only looks up a session. If your application creates a session +after an unsuccessful lookup, another client may create the same name between +those operations. Handle the creation error instead of assuming the lookup +reserves the name. + +For a complete program that starts and owns its server, see +[Capture pane output](/examples/capture-pane-output/). diff --git a/site/src/content/docs/ports/lua/guides/attaching-to-tmux.md b/site/src/content/docs/ports/lua/guides/attaching-to-tmux.md new file mode 100644 index 00000000..926731f7 --- /dev/null +++ b/site/src/content/docs/ports/lua/guides/attaching-to-tmux.md @@ -0,0 +1,122 @@ +--- +port: lua +route: guides/attaching-to-tmux +title: Attaching to tmux +description: Connect to an existing tmux server and find a session with Lua. +sidebar: + label: Attaching to tmux + group: Guides + order: 3 +tableOfContents: true +--- + +Connect a `Server` to an explicit socket and find the existing `work` session. +The program prints its name and leaves the tmux server running. It reports an +error if the connection fails or the session is absent. + +This controls tmux from your program. To open a session in your terminal, use +`tmux attach-session`; the [shared guide](../../../../guides/attaching-to-tmux/) covers +interactive attachment and detaching. + + + +## Connect to an existing server + +Save the complete program as `connect.lua`. `LIBTMUX_SOCKET_PATH` selects +the existing server. The launcher below supplies a private socket for trying +the example. + +```lua title="connect.lua" +local adapter = require("libtmux.runtime.luv") + +local function must(value, err) + if err ~= nil then error(tostring(err), 0) end + return value +end + +local socket = assert(os.getenv("LIBTMUX_SOCKET_PATH"), "set LIBTMUX_SOCKET_PATH") +local binary = assert(os.getenv("TMUX_BIN"), "set TMUX_BIN to the absolute tmux path") + +must(adapter.run(function(runtime) + local server = must(runtime:connect({ binary = binary, socket_path = socket }):await()) + local snapshot, capture_error = server:snapshot({ strict = true }):await() + local closed, close_error = server:close():await() + must(snapshot, capture_error) + must(closed, close_error) + for _, session in ipairs(snapshot.sessions) do + if session.name == "work" then + print("work") + return true + end + end + error("The work session does not exist", 0) +end)) +``` + +## Setup and run + +Use an empty directory on Linux with Git and tmux 3.2a or newer installed. + +This example was checked with Lua 5.5.1, luv 1.52.1-0. + +Save the launcher as `run.sh`. It starts an isolated tmux server, runs the +program, checks that the session still exists, then stops only that server. +Cleanup runs after failures too. A failed shutdown keeps its socket directory +and prints its location for inspection. + +```sh title="run.sh" +#!/bin/sh +set -eu + +binary=$(command -v tmux) +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-lua-attach.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || exit 1 + exit "$status" +} +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +unset TMUX TMUX_PANE +export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" +"$binary" -S "$socket" -f /dev/null new-session -d -s work /bin/cat +"$@" +"$binary" -S "$socket" has-session -t '=work' +``` + +Fetch the verified library revision, build, and run: + +```console +$ git clone https://github.com/libtmux/libtmux-lua libtmux-source && + git -C libtmux-source checkout 5baa3f9b830ebdbc76fb50b5b3d7a5ad3f76d443 && + luarocks --tree ./rocks install luv 1.52.1-0 && + (cd libtmux-source && + luarocks --tree ../rocks make rockspecs/libtmux-scm-1.rockspec) && + eval "$(luarocks --tree ./rocks path)" && + sh run.sh lua connect.lua +``` + +The program prints `work`. To use an existing server of your own, set +`LIBTMUX_SOCKET_PATH` to its socket and run the program without the launcher. +Also set `TMUX_BIN` to the absolute path of the tmux executable. +The launcher is responsible for the demonstration server's lifetime. + + + +## Find or create a session + +The example only looks up a session. If your application creates a session +after an unsuccessful lookup, another client may create the same name between +those operations. Handle the creation error instead of assuming the lookup +reserves the name. + +For a complete program that starts and owns its server, see +[Capture pane output](/examples/capture-pane-output/). diff --git a/site/src/content/docs/ports/py/guides/attaching-to-tmux.md b/site/src/content/docs/ports/py/guides/attaching-to-tmux.md new file mode 100644 index 00000000..62312f08 --- /dev/null +++ b/site/src/content/docs/ports/py/guides/attaching-to-tmux.md @@ -0,0 +1,101 @@ +--- +port: py +route: guides/attaching-to-tmux +title: Attaching to tmux +description: Connect to an existing tmux server and find a session with Python. +sidebar: + label: Attaching to tmux + group: Guides + order: 3 +tableOfContents: true +--- + +Connect a `Server` to an explicit socket and find the existing `work` session. +The program prints its name and leaves the tmux server running. It reports an +error if the connection fails or the session is absent. + +This controls tmux from your program. To open a session in your terminal, use +`tmux attach-session`; the [shared guide](../../../../guides/attaching-to-tmux/) covers +interactive attachment and detaching. + + + +## Connect to an existing server + +Save the complete program as `connect.py`. `LIBTMUX_SOCKET_PATH` selects +the existing server. The launcher below supplies a private socket for trying +the example. + +```python title="connect.py" +import os + +import libtmux + +server = libtmux.Server(socket_path=os.environ["LIBTMUX_SOCKET_PATH"]) +session = server.sessions.get(session_name="work") +print(session.session_name) +``` + +## Setup and run + +Use an empty directory on Linux with Git and tmux 3.2a or newer installed. + +This example was checked with Python 3.14.6. + +Save the launcher as `run.sh`. It starts an isolated tmux server, runs the +program, checks that the session still exists, then stops only that server. +Cleanup runs after failures too. A failed shutdown keeps its socket directory +and prints its location for inspection. + +```sh title="run.sh" +#!/bin/sh +set -eu + +binary=$(command -v tmux) +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-py-attach.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || exit 1 + exit "$status" +} +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +unset TMUX TMUX_PANE +export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" +"$binary" -S "$socket" -f /dev/null new-session -d -s work /bin/cat +"$@" +"$binary" -S "$socket" has-session -t '=work' +``` + +Fetch the verified library revision, build, and run: + +```console +$ sh run.sh uv run \ + --with 'libtmux @ git+https://github.com/tmux-python/libtmux@9fdd083a8181a827889337b63cd4e6daea661a8c' \ + connect.py +``` + +The program prints `work`. To use an existing server of your own, set +`LIBTMUX_SOCKET_PATH` to its socket and run the program without the launcher. +That launcher is responsible for the demonstration server's lifetime. + + + +## Find or create a session + +The example only looks up a session. If your application creates a session +after an unsuccessful lookup, another client may create the same name between +those operations. Handle the creation error instead of assuming the lookup +reserves the name. + +For a complete program that starts and owns its server, see +[Capture pane output](/examples/capture-pane-output/). Continue with +[Sending keys](/guides/sending-keys/) once you have a pane handle. diff --git a/site/src/content/docs/ports/rs/guides/attaching-to-tmux.md b/site/src/content/docs/ports/rs/guides/attaching-to-tmux.md new file mode 100644 index 00000000..31818f7d --- /dev/null +++ b/site/src/content/docs/ports/rs/guides/attaching-to-tmux.md @@ -0,0 +1,134 @@ +--- +port: rs +route: guides/attaching-to-tmux +title: Attaching to tmux +description: Connect to an existing tmux server and find a session with Rust. +sidebar: + label: Attaching to tmux + group: Guides + order: 3 +tableOfContents: true +--- + +Connect a `Server` to an explicit socket and find the existing `work` session. +The program prints its name and leaves the tmux server running. It reports an +error if the connection fails or the session is absent. + +This controls tmux from your program. To open a session in your terminal, use +`tmux attach-session`; the [shared guide](../../../../guides/attaching-to-tmux/) covers +interactive attachment and detaching. + + + +## Connect to an existing server + +Save the complete program as `src/main.rs`. `LIBTMUX_SOCKET_PATH` selects +the existing server. The launcher below supplies a private socket for trying +the example. + +```rust title="src/main.rs" +use std::error::Error; +use std::time::Duration; + +use libtmux::Server; +use tokio::time::timeout; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let socket = std::env::var("LIBTMUX_SOCKET_PATH")?; + let server = Server::builder().socket_path(socket).build()?; + let result = timeout(Duration::from_secs(5), async { + let sessions = server.sessions().await?; + if !sessions + .iter() + .any(|session| session.name().as_bytes() == b"work") + { + return Err("The work session does not exist".into()); + } + println!("work"); + Ok::<_, Box>(()) + }) + .await; + let closed = server.shutdown().await; + result??; + closed?; + Ok(()) +} +``` + +## Setup and run + +Use an empty directory on Linux with Git and tmux 3.2a or newer installed. + +This example was checked with Rust 1.97.1. + +Save this file beside the program using the displayed filename. + +```toml title="Cargo.toml" +[package] +name = "connect-example" +version = "0.1.0" +edition = "2024" + +[dependencies] +libtmux = { path = "libtmux/crates/libtmux" } +tokio = { version = "1.53.1", features = ["macros", "rt-multi-thread", "time"] } +``` + +Save the launcher as `run.sh`. It starts an isolated tmux server, runs the +program, checks that the session still exists, then stops only that server. +Cleanup runs after failures too. A failed shutdown keeps its socket directory +and prints its location for inspection. + +```sh title="run.sh" +#!/bin/sh +set -eu + +binary=$(command -v tmux) +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-rs-attach.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || exit 1 + exit "$status" +} +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +unset TMUX TMUX_PANE +export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" +"$binary" -S "$socket" -f /dev/null new-session -d -s work /bin/cat +"$@" +"$binary" -S "$socket" has-session -t '=work' +``` + +Fetch the verified library revision, build, and run: + +```console +$ git clone https://github.com/libtmux/libtmux-rs libtmux && + git -C libtmux checkout d4e08b4eaab62ef4eeedab79b47973ae9a1de310 && + sh run.sh cargo +1.97.1 run +``` + +The program prints `work`. To use an existing server of your own, set +`LIBTMUX_SOCKET_PATH` to its socket and run the program without the launcher. +That launcher is responsible for the demonstration server's lifetime. + + + +## Find or create a session + +The example only looks up a session. If your application creates a session +after an unsuccessful lookup, another client may create the same name between +those operations. Handle the creation error instead of assuming the lookup +reserves the name. + +For a complete program that starts and owns its server, see +[Capture pane output](/examples/capture-pane-output/). Continue with +[Sending keys](/guides/sending-keys/) once you have a pane handle. diff --git a/site/src/content/docs/ports/rs/workspace/internals/examples.md b/site/src/content/docs/ports/rs/workspace/internals/examples.md index 0ca1a15b..5dd901e4 100644 --- a/site/src/content/docs/ports/rs/workspace/internals/examples.md +++ b/site/src/content/docs/ports/rs/workspace/internals/examples.md @@ -36,6 +36,10 @@ Create the project manifest. Both library crates use that source tree. The `test-support` feature provides the public `TestServer` helper, which creates and owns the isolated server used by this example. +`TestServer` is temporary example scaffolding. In an application, use a +[`Server`](../../../reference/server-server/) connected to the tmux server you manage. +Future versions of this example will use that regular server object directly. + ```toml title="Cargo.toml" [package] name = "workspace-example" diff --git a/site/src/content/docs/ports/ruby/guides/attaching-to-tmux.md b/site/src/content/docs/ports/ruby/guides/attaching-to-tmux.md new file mode 100644 index 00000000..3859d042 --- /dev/null +++ b/site/src/content/docs/ports/ruby/guides/attaching-to-tmux.md @@ -0,0 +1,110 @@ +--- +port: ruby +route: guides/attaching-to-tmux +title: Attaching to tmux +description: Connect to an existing tmux server and find a session with Ruby. +sidebar: + label: Attaching to tmux + group: Guides + order: 3 +tableOfContents: true +--- + +Connect a `Server` to an explicit socket and find the existing `work` session. +The program prints its name and leaves the tmux server running. It reports an +error if the connection fails or the session is absent. + +This controls tmux from your program. To open a session in your terminal, use +`tmux attach-session`; the [shared guide](../../../../guides/attaching-to-tmux/) covers +interactive attachment and detaching. + + + +## Connect to an existing server + +Save the complete program as `connect.rb`. `LIBTMUX_SOCKET_PATH` selects +the existing server. The launcher below supplies a private socket for trying +the example. + +```ruby title="connect.rb" +require "libtmux" + +LibTmux::Server.open(socket_path: ENV.fetch("LIBTMUX_SOCKET_PATH")) do |server| + session = server.snapshot.sessions.find { |candidate| candidate.name == "work" } + raise "The work session does not exist" unless session + puts session.name +end +``` + +## Setup and run + +Use an empty directory on Linux with Git and tmux 3.2a or newer installed. + +This example was checked with Ruby 4.0.7, Bundler. + +Save this file beside the program using the displayed filename. + +```ruby title="Gemfile" +source "https://rubygems.org" + +gem "libtmux", path: "libtmux-source/gems/libtmux" +``` + +Save the launcher as `run.sh`. It starts an isolated tmux server, runs the +program, checks that the session still exists, then stops only that server. +Cleanup runs after failures too. A failed shutdown keeps its socket directory +and prints its location for inspection. + +```sh title="run.sh" +#!/bin/sh +set -eu + +binary=$(command -v tmux) +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-ruby-attach.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || exit 1 + exit "$status" +} +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +unset TMUX TMUX_PANE +export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" +"$binary" -S "$socket" -f /dev/null new-session -d -s work /bin/cat +"$@" +"$binary" -S "$socket" has-session -t '=work' +``` + +Fetch the verified library revision, build, and run: + +```console +$ git clone https://github.com/libtmux/libtmux-ruby libtmux-source && + git -C libtmux-source checkout 2599d45369515aaf2fd5793bfe71cdc20de641b6 && + bundle config set --local path vendor/bundle && + bundle install && + sh run.sh bundle exec ruby connect.rb +``` + +The program prints `work`. To use an existing server of your own, set +`LIBTMUX_SOCKET_PATH` to its socket and run the program without the launcher. +That launcher is responsible for the demonstration server's lifetime. + + + +## Find or create a session + +The example only looks up a session. If your application creates a session +after an unsuccessful lookup, another client may create the same name between +those operations. Handle the creation error instead of assuming the lookup +reserves the name. + +For a complete program that starts and owns its server, see +[Capture pane output](/examples/capture-pane-output/). diff --git a/site/src/content/docs/ports/scala/guides/attaching-to-tmux.md b/site/src/content/docs/ports/scala/guides/attaching-to-tmux.md new file mode 100644 index 00000000..9a1db09e --- /dev/null +++ b/site/src/content/docs/ports/scala/guides/attaching-to-tmux.md @@ -0,0 +1,152 @@ +--- +port: scala +route: guides/attaching-to-tmux +title: Attaching to tmux +description: Connect to an existing tmux server and find a session with Scala. +sidebar: + label: Attaching to tmux + group: Guides + order: 3 +tableOfContents: true +--- + +Connect a `Server` to an explicit socket and find the existing `work` session. +The program prints its name and leaves the tmux server running. It reports an +error if the connection fails or the session is absent. + +This controls tmux from your program. To open a session in your terminal, use +`tmux attach-session`; the [shared guide](../../../../guides/attaching-to-tmux/) covers +interactive attachment and detaching. + + + +## Connect to an existing server + +Save the complete program as `Connect.scala`. `LIBTMUX_SOCKET_PATH` selects +the existing server. The launcher below supplies a private socket for trying +the example. + +```scala title="Connect.scala" +import io.github.libtmux.{ServerConfig, ServerEndpoint} +import io.github.libtmux.scaladsl.* +import java.nio.file.Path +import java.time.Duration +import scala.util.Using + +object Connect { + def main(args: Array[String]): Unit = { + val socket = sys.env.getOrElse("LIBTMUX_SOCKET_PATH", + throw new IllegalArgumentException("Set LIBTMUX_SOCKET_PATH to an existing socket")) + val config = ServerConfig.builder() + .endpoint(ServerEndpoint.socketPath(Path.of(socket))) + .defaultTimeout(Duration.ofSeconds(5)) + .build() + Using.resource(Server.open(config)) { server => + val session = server.sessions().find(_.name == "work").getOrElse( + throw new IllegalStateException("The work session does not exist")) + println(session.name) + } + } +} +``` + +## Setup and run + +Use an empty directory on Linux with Git and tmux 3.2a or newer installed. + +This example was checked with JDK 25.0.3, Scala 3.9.0. + +Save this file beside the program using the displayed filename. + +```kotlin title="build.gradle.kts" +plugins { + application + scala +} + +repositories { mavenCentral() } +dependencies { + implementation("org.scala-lang:scala3-library_3:3.9.0") + implementation("io.github.libtmux:libtmux-scala_3:0.0.1-alpha.17-SNAPSHOT") +} +java { toolchain { languageVersion.set(JavaLanguageVersion.of(25)) } } +sourceSets.main { scala.srcDir("."); scala.include("Connect.scala") } +application { mainClass.set("Connect") } +``` + +Save this file beside the program using the displayed filename. + +```kotlin title="settings.gradle.kts" +rootProject.name = "connect" +includeBuild("libtmux-source") { + dependencySubstitution { + substitute(module("io.github.libtmux:libtmux-scala_3")) + .using(project(":libtmux-scala")) + } +} +``` + +Save this file beside the program using the displayed filename. + +```properties title="gradle.properties" +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 +kotlin.daemon.jvmargs=-Xmx2g +org.gradle.workers.max=2 +``` + +Save the launcher as `run.sh`. It starts an isolated tmux server, runs the +program, checks that the session still exists, then stops only that server. +Cleanup runs after failures too. A failed shutdown keeps its socket directory +and prints its location for inspection. + +```sh title="run.sh" +#!/bin/sh +set -eu + +binary=$(command -v tmux) +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-scala-attach.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || exit 1 + exit "$status" +} +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +unset TMUX TMUX_PANE +export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" +"$binary" -S "$socket" -f /dev/null new-session -d -s work /bin/cat +"$@" +"$binary" -S "$socket" has-session -t '=work' +``` + +Fetch the verified library revision, build, and run: + +```console +$ git clone https://github.com/libtmux/libtmux-java libtmux-source && + git -C libtmux-source checkout 85ebf6955e56703c5be74e2afd34a18309044741 && + sh run.sh ./libtmux-source/gradlew --project-dir . run --console=plain --max-workers=2 +``` + +The program prints `work`. To use an existing server of your own, set +`LIBTMUX_SOCKET_PATH` to its socket and run the program without the launcher. +That launcher is responsible for the demonstration server's lifetime. + + + +## Find or create a session + +The example only looks up a session. If your application creates a session +after an unsuccessful lookup, another client may create the same name between +those operations. Handle the creation error instead of assuming the lookup +reserves the name. + +For a complete program that starts and owns its server, see +[Capture pane output](/examples/capture-pane-output/). diff --git a/site/src/content/docs/ports/swift/guides/attaching-to-tmux.md b/site/src/content/docs/ports/swift/guides/attaching-to-tmux.md new file mode 100644 index 00000000..4f7c6abb --- /dev/null +++ b/site/src/content/docs/ports/swift/guides/attaching-to-tmux.md @@ -0,0 +1,133 @@ +--- +port: swift +route: guides/attaching-to-tmux +title: Attaching to tmux +description: Connect to an existing tmux server and find a session with Swift. +sidebar: + label: Attaching to tmux + group: Guides + order: 3 +tableOfContents: true +--- + +Connect a `Server` to an explicit socket and find the existing `work` session. +The program prints its name and leaves the tmux server running. It reports an +error if the connection fails or the session is absent. + +This controls tmux from your program. To open a session in your terminal, use +`tmux attach-session`; the [shared guide](../../../../guides/attaching-to-tmux/) covers +interactive attachment and detaching. + + + +## Connect to an existing server + +Save the complete program as `Sources/Connect/Connect.swift`. `LIBTMUX_SOCKET_PATH` selects +the existing server. The launcher below supplies a private socket for trying +the example. + +```swift title="Connect.swift" +import Foundation +import LibTmux + +enum ConnectError: Error { + case failed(String) +} + +@main +struct Connect { + static func main() async throws { + guard let socket = ProcessInfo.processInfo.environment["LIBTMUX_SOCKET_PATH"] else { + throw ConnectError.failed("Set LIBTMUX_SOCKET_PATH to an existing socket") + } + let server = try Server(socketPath: socket) + guard try await server.hasSession("=work") else { + throw ConnectError.failed("The work session does not exist") + } + print("work") + } +} +``` + +## Setup and run + +Use an empty directory on Linux with Git and tmux 3.2a or newer installed. + +This example was checked with Swift 6.2.4. + +Save this file beside the program using the displayed filename. + +```swift title="Package.swift" +// swift-tools-version: 6.2 +import PackageDescription + +let package = Package( + name: "ConnectExample", + platforms: [.macOS(.v13)], + dependencies: [.package(path: "libtmux-source")], + targets: [ + .executableTarget( + name: "Connect", + dependencies: [.product(name: "LibTmux", package: "libtmux-source")] + ), + ] +) +``` + +Save the launcher as `run.sh`. It starts an isolated tmux server, runs the +program, checks that the session still exists, then stops only that server. +Cleanup runs after failures too. A failed shutdown keeps its socket directory +and prints its location for inspection. + +```sh title="run.sh" +#!/bin/sh +set -eu + +binary=$(command -v tmux) +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-swift-attach.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || exit 1 + exit "$status" +} +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +unset TMUX TMUX_PANE +export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" +"$binary" -S "$socket" -f /dev/null new-session -d -s work /bin/cat +"$@" +"$binary" -S "$socket" has-session -t '=work' +``` + +Fetch the verified library revision, build, and run: + +```console +$ git clone https://github.com/libtmux/libtmux-swift libtmux-source && + git -C libtmux-source checkout 254f8b2be7eb60cacc3ffcb3ea8e456784f582df && + sh run.sh swift run --jobs 2 Connect +``` + +The program prints `work`. To use an existing server of your own, set +`LIBTMUX_SOCKET_PATH` to its socket and run the program without the launcher. +That launcher is responsible for the demonstration server's lifetime. + + + +## Find or create a session + +The example only looks up a session. If your application creates a session +after an unsuccessful lookup, another client may create the same name between +those operations. Handle the creation error instead of assuming the lookup +reserves the name. + +For a complete program that starts and owns its server, see +[Capture pane output](/examples/capture-pane-output/). Continue with +[Sending keys](/guides/sending-keys/) once you have a pane handle. diff --git a/site/src/content/docs/ports/ts/guides/attaching-to-tmux.md b/site/src/content/docs/ports/ts/guides/attaching-to-tmux.md new file mode 100644 index 00000000..c43343b7 --- /dev/null +++ b/site/src/content/docs/ports/ts/guides/attaching-to-tmux.md @@ -0,0 +1,109 @@ +--- +port: ts +route: guides/attaching-to-tmux +title: Attaching to tmux +description: Connect to an existing tmux server and find a session with TypeScript. +sidebar: + label: Attaching to tmux + group: Guides + order: 3 +tableOfContents: true +--- + +Connect a `Server` to an explicit socket and find the existing `work` session. +The program prints its name and leaves the tmux server running. It reports an +error if the connection fails or the session is absent. + +This controls tmux from your program. To open a session in your terminal, use +`tmux attach-session`; the [shared guide](../../../../guides/attaching-to-tmux/) covers +interactive attachment and detaching. + + + +## Connect to an existing server + +Save the complete program as `connect.ts`. `LIBTMUX_SOCKET_PATH` selects +the existing server. The launcher below supplies a private socket for trying +the example. + +```typescript title="connect.ts" +import { Server } from "libtmux"; + +const socketPath = process.env.LIBTMUX_SOCKET_PATH; +if (!socketPath) throw new Error("Set LIBTMUX_SOCKET_PATH to an existing socket"); +const server = new Server({ socketPath, timeoutMs: 5_000 }); +const snapshot = await server.snapshot({ signal: AbortSignal.timeout(5_000) }); +const session = snapshot.sessions.one({ name: "work" }); +console.log(session.name); +``` + +## Setup and run + +Use an empty directory on Linux with Git and tmux 3.2a or newer installed. + +This example was checked with Bun 1.4.2. + +Save this file beside the program using the displayed filename. + +```json title="package.json" +{"type":"module","dependencies":{"libtmux":"file:./libtmux/packages/libtmux"}} +``` + +Save the launcher as `run.sh`. It starts an isolated tmux server, runs the +program, checks that the session still exists, then stops only that server. +Cleanup runs after failures too. A failed shutdown keeps its socket directory +and prints its location for inspection. + +```sh title="run.sh" +#!/bin/sh +set -eu + +binary=$(command -v tmux) +directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-ts-attach.XXXXXX") +socket="$directory/tmux.sock" + +cleanup() { + status=$? + trap - 0 HUP INT TERM + if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then + printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 + exit 1 + fi + rm -rf "$directory" || exit 1 + exit "$status" +} +trap cleanup 0 +trap 'exit 1' HUP INT TERM + +unset TMUX TMUX_PANE +export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" +"$binary" -S "$socket" -f /dev/null new-session -d -s work /bin/cat +"$@" +"$binary" -S "$socket" has-session -t '=work' +``` + +Fetch the verified library revision, build, and run: + +```console +$ git clone https://github.com/libtmux/libtmux-ts libtmux && + git -C libtmux checkout 3fe1ca654b81b8cbf4a13b777a001a3298c87a6f && + bun install && + sh run.sh bun run connect.ts +``` + +The program prints `work`. To use an existing server of your own, set +`LIBTMUX_SOCKET_PATH` to its socket and run the program without the launcher. +That launcher is responsible for the demonstration server's lifetime. + + + +## Find or create a session + +The example only looks up a session. If your application creates a session +after an unsuccessful lookup, another client may create the same name between +those operations. Handle the creation error instead of assuming the lookup +reserves the name. + +For a complete program that starts and owns its server, see +[Capture pane output](/examples/capture-pane-output/). Continue with +[Sending keys](/guides/sending-keys/) once you have a pane handle. diff --git a/site/src/data/mentions.json b/site/src/data/mentions.json index 09710aea..e366831a 100644 --- a/site/src/data/mentions.json +++ b/site/src/data/mentions.json @@ -259,6 +259,20 @@ "title": "Traversal", "section": "topics" }, + { + "port": "cxx", + "symbol": "libtmux::Server", + "page": "/cxx/latest/examples/capture-pane-output/", + "title": "Capture pane output", + "section": "examples" + }, + { + "port": "cxx", + "symbol": "libtmux::Server", + "page": "/cxx/latest/guides/attaching-to-tmux/", + "title": "Attaching to tmux", + "section": "guides" + }, { "port": "cxx", "symbol": "libtmux::Server", @@ -273,6 +287,13 @@ "title": "C++ workspace internals", "section": "ports" }, + { + "port": "cxx", + "symbol": "libtmux::Server", + "page": "/cxx/latest/workspace/internals/examples/", + "title": "C++ workspace builder examples", + "section": "ports" + }, { "port": "cxx", "symbol": "libtmux::Server", @@ -434,6 +455,20 @@ "title": "Filtering and queries", "section": "concepts" }, + { + "port": "cxx", + "symbol": "libtmux::test::ScopedTmuxServer", + "page": "/cxx/latest/examples/capture-pane-output/", + "title": "Capture pane output", + "section": "examples" + }, + { + "port": "cxx", + "symbol": "libtmux::test::ScopedTmuxServer", + "page": "/cxx/latest/workspace/internals/examples/", + "title": "C++ workspace builder examples", + "section": "ports" + }, { "port": "cxx", "symbol": "libtmux::test::ScopedTmuxServer", @@ -777,13 +812,6 @@ "title": ".NET MCP API", "section": "ports" }, - { - "port": "dotnet", - "symbol": "LibTmux.NewSessionRequest.ReplaceExisting", - "page": "/guides/attaching-to-tmux/", - "title": "Attaching to tmux", - "section": "guides" - }, { "port": "dotnet", "symbol": "LibTmux.OwnedSessionScope", @@ -868,6 +896,13 @@ "title": "Pane interaction", "section": "topics" }, + { + "port": "dotnet", + "symbol": "LibTmux.Server", + "page": "/dotnet/latest/guides/attaching-to-tmux/", + "title": "Attaching to tmux", + "section": "guides" + }, { "port": "dotnet", "symbol": "LibTmux.Server", @@ -896,13 +931,6 @@ "title": "Use the .NET workspace builder", "section": "ports" }, - { - "port": "dotnet", - "symbol": "LibTmux.Server.CreateSessionAsync", - "page": "/guides/attaching-to-tmux/", - "title": "Attaching to tmux", - "section": "guides" - }, { "port": "dotnet", "symbol": "LibTmux.Server.EnterControlModeAsync", @@ -924,13 +952,6 @@ "title": "Traversal", "section": "topics" }, - { - "port": "dotnet", - "symbol": "LibTmux.Server.HasSessionAsync", - "page": "/guides/attaching-to-tmux/", - "title": "Attaching to tmux", - "section": "guides" - }, { "port": "dotnet", "symbol": "LibTmux.Server.IsAliveAsync", @@ -1680,6 +1701,13 @@ "title": "Control mode vs one-shot", "section": "concepts" }, + { + "port": "go", + "symbol": "tmux.Server", + "page": "/go/latest/guides/attaching-to-tmux/", + "title": "Attaching to tmux", + "section": "guides" + }, { "port": "go", "symbol": "tmux.Server", @@ -2471,6 +2499,13 @@ "title": "Testing with libtmux", "section": "guides" }, + { + "port": "java", + "symbol": "io.github.libtmux.Server.Server", + "page": "/java/latest/guides/attaching-to-tmux/", + "title": "Attaching to tmux", + "section": "guides" + }, { "port": "java", "symbol": "io.github.libtmux.Server.Server", @@ -4263,6 +4298,13 @@ "title": "Testing with libtmux", "section": "guides" }, + { + "port": "py", + "symbol": "libtmux.server.Server", + "page": "/py/latest/guides/attaching-to-tmux/", + "title": "Attaching to tmux", + "section": "guides" + }, { "port": "py", "symbol": "libtmux.server.Server", @@ -4417,13 +4459,6 @@ "title": "Traversal", "section": "topics" }, - { - "port": "py", - "symbol": "libtmux.session.Session.attach", - "page": "/guides/attaching-to-tmux/", - "title": "Attaching to tmux", - "section": "guides" - }, { "port": "py", "symbol": "libtmux.session.Session.from_env", @@ -5180,6 +5215,13 @@ "title": "Waiting and retrying", "section": "topics" }, + { + "port": "rs", + "symbol": "server.Server", + "page": "/rs/latest/guides/attaching-to-tmux/", + "title": "Attaching to tmux", + "section": "guides" + }, { "port": "rs", "symbol": "server.Server", @@ -5201,6 +5243,13 @@ "title": "Rust workspace internals", "section": "ports" }, + { + "port": "rs", + "symbol": "server.Server", + "page": "/rs/latest/workspace/internals/examples/", + "title": "Rust workspace builder examples", + "section": "ports" + }, { "port": "rs", "symbol": "server.Server", @@ -6132,6 +6181,13 @@ "title": "Swift workspace builder API", "section": "ports" }, + { + "port": "swift", + "symbol": "Server", + "page": "/swift/latest/guides/attaching-to-tmux/", + "title": "Attaching to tmux", + "section": "guides" + }, { "port": "swift", "symbol": "Server", @@ -7063,6 +7119,13 @@ "title": "Testing with libtmux", "section": "guides" }, + { + "port": "ts", + "symbol": "server.Server", + "page": "/ts/latest/guides/attaching-to-tmux/", + "title": "Attaching to tmux", + "section": "guides" + }, { "port": "ts", "symbol": "server.Server", diff --git a/site/test/complete-examples.test.ts b/site/test/complete-examples.test.ts index 26db49eb..8b4fe985 100644 --- a/site/test/complete-examples.test.ts +++ b/site/test/complete-examples.test.ts @@ -4,6 +4,7 @@ import { readFileSync } from 'node:fs' import { Window } from 'happy-dom' import { afterEach, describe, expect, it, vi } from 'vitest' import receipt from './fixtures/capture-examples.json' +import attach from './fixtures/attach-examples.json' import products from './fixtures/product-examples.json' import { remarkPortCode, resolvePortCode } from '../src/plugins/remark-port-code.mjs' import { rehypeCodeTabs } from '../src/plugins/rehype-code-tabs.mjs' @@ -16,7 +17,7 @@ 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, ...products.examples] +const examples = [...receipt.examples, ...attach.examples, ...products.examples] afterEach(() => vi.unstubAllEnvs()) @@ -37,10 +38,12 @@ describe('verified complete programs', () => { const commands = [...selected.matchAll(/^```console\n([\s\S]*?)^```/gm)] .map((match) => match[1].replace(/^\$ /gm, '').trim()) expect(commands).toEqual(example.shellRecipe) - const program = blocks.find((block) => block.title === example.files[0].name)! - const comment = ['python', 'sh'].includes(example.language) ? /^\s*#/ : /^\s*(?:\/\/|\/\*|\* )/ - for (const line of program.code.split('\n').filter((line) => comment.test(line))) { - expect(line.length, `${example.port}: ${line}`).toBeLessThanOrEqual(80) + for (const block of blocks) { + const comment = ['python', 'sh', 'ruby', 'toml', 'cmake', 'yaml', 'properties'].includes(block.language) + ? /^\s*#/ : block.language === 'lua' ? /^\s*--/ : /^\s*(?:\/\/|\/\*|\* )/ + for (const line of block.code.split('\n').filter((line) => comment.test(line))) { + expect(line.replaceAll('\t', ' ').length, `${example.port}: ${line}`).toBeLessThanOrEqual(100) + } } }) @@ -71,7 +74,7 @@ describe('verified complete programs', () => { } }) - it('keeps the root about tmux and routes each complete program to its port', async () => { + 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) const blocks = fences(bodyOf(receipt.page)) diff --git a/site/test/fixtures/attach-examples.json b/site/test/fixtures/attach-examples.json new file mode 100644 index 00000000..73b331c3 --- /dev/null +++ b/site/test/fixtures/attach-examples.json @@ -0,0 +1,449 @@ +{ + "page": "guides/attaching-to-tmux", + "scope": "Complete existing-server consumers with their displayed setup and isolated shell launcher.", + "examples": [ + { + "port": "py", + "language": "python", + "sourceRevision": "9fdd083a8181a827889337b63cd4e6daea661a8c", + "sourceRepository": "tmux-python/libtmux", + "toolchain": [ + "Python 3.14.6" + ], + "page": "ports/py/guides/attaching-to-tmux", + "shellRecipe": [ + "sh run.sh uv run \\\n --with 'libtmux @ git+https://github.com/tmux-python/libtmux@9fdd083a8181a827889337b63cd4e6daea661a8c' \\\n connect.py" + ], + "expectedOutput": "work", + "files": [ + { + "name": "connect.py", + "sha256": "85c37335c74572af5606ad44a165fe097ad3b2737a061a75a86a9eba6f91af52" + }, + { + "name": "run.sh", + "sha256": "947db2704b348ea51541e4cffe5ac613fb0b2d5703affe8abd0f35fc8286085e" + } + ], + "tmux": [ + "3.2a", + "3.7c" + ] + }, + { + "port": "ts", + "language": "typescript", + "sourceRevision": "3fe1ca654b81b8cbf4a13b777a001a3298c87a6f", + "sourceRepository": "libtmux/libtmux-ts", + "toolchain": [ + "Bun 1.4.2" + ], + "page": "ports/ts/guides/attaching-to-tmux", + "shellRecipe": [ + "git clone https://github.com/libtmux/libtmux-ts libtmux &&\n git -C libtmux checkout 3fe1ca654b81b8cbf4a13b777a001a3298c87a6f &&\n bun install &&\n sh run.sh bun run connect.ts" + ], + "expectedOutput": "work", + "files": [ + { + "name": "connect.ts", + "sha256": "bd4f946b8fc2a24e1aab9d366402fa1260a3a05542023f3454e0686c72ba3d9d" + }, + { + "name": "package.json", + "sha256": "acb06879bf3e3e5ce85125d64a72ff5b2579e87a66085961b1f7b88eaa813020" + }, + { + "name": "run.sh", + "sha256": "124a197acc79bf28595ebf8ea43cc9b89565d2f97ca5cc5ae64257674bdad539" + } + ], + "tmux": [ + "3.2a", + "3.7c" + ] + }, + { + "port": "go", + "language": "go", + "sourceRevision": "bb06e26e116e941813ca40bf45e7e3a47d38f52a", + "sourceRepository": "libtmux/libtmux-go", + "toolchain": [ + "Go 1.26.8" + ], + "page": "ports/go/guides/attaching-to-tmux", + "shellRecipe": [ + "git clone https://github.com/libtmux/libtmux-go libtmux &&\n git -C libtmux checkout bb06e26e116e941813ca40bf45e7e3a47d38f52a &&\n GOWORK=off go mod tidy &&\n sh run.sh env GOWORK=off go run ." + ], + "expectedOutput": "work", + "files": [ + { + "name": "main.go", + "sha256": "c94705da738b099fc78dd646ee8c49e1405fea1da2259bdc327a4849684b51cc" + }, + { + "name": "go.mod", + "sha256": "d8975aa3076f5cf4a15169fb558fd49404644e1cb5127299536a99c7253de4e4" + }, + { + "name": "run.sh", + "sha256": "2163f09a3a6ba881afb8d0e9750910b652f39c762d31013f2dbc4b3868c3c565" + } + ], + "tmux": [ + "3.2a", + "3.7c" + ] + }, + { + "port": "rs", + "language": "rust", + "sourceRevision": "d4e08b4eaab62ef4eeedab79b47973ae9a1de310", + "sourceRepository": "libtmux/libtmux-rs", + "toolchain": [ + "Rust 1.97.1" + ], + "page": "ports/rs/guides/attaching-to-tmux", + "shellRecipe": [ + "git clone https://github.com/libtmux/libtmux-rs libtmux &&\n git -C libtmux checkout d4e08b4eaab62ef4eeedab79b47973ae9a1de310 &&\n sh run.sh cargo +1.97.1 run" + ], + "expectedOutput": "work", + "files": [ + { + "name": "src/main.rs", + "sha256": "931e123008ee498523d89f0036ed480f4a9da0a47e0572a75f58df6034dc1ff1" + }, + { + "name": "Cargo.toml", + "sha256": "b1126e5db8c76db064324255f98bc382a9d2ade716d89f19e8767baeebb170ee" + }, + { + "name": "run.sh", + "sha256": "d984d9482d78cb452286b071afad4aca2d21ee271d44920b0818fabe8e6cc511" + } + ], + "tmux": [ + "3.2a", + "3.7c" + ] + }, + { + "port": "java", + "language": "java", + "sourceRevision": "842228310449e879ebcaa3f910597757c9dbffd6", + "sourceRepository": "libtmux/libtmux-java", + "toolchain": [ + "JDK 25.0.3", + "library bytecode target Java 21" + ], + "page": "ports/java/guides/attaching-to-tmux", + "shellRecipe": [ + "git clone https://github.com/libtmux/libtmux-java libtmux &&\n git -C libtmux checkout 842228310449e879ebcaa3f910597757c9dbffd6 &&\n (cd libtmux && ./gradlew :libtmux:jar) &&\n sh run.sh java --class-path 'libtmux/libtmux/build/libs/*' Connect.java" + ], + "expectedOutput": "work", + "files": [ + { + "name": "Connect.java", + "sha256": "0dd7f4fef844f89ed6b6f424f3bce74daa43beb5280f43b12ab44cc1f4bf56a3" + }, + { + "name": "run.sh", + "sha256": "c7c6c6c7798d10dd389beaa6a5843f145a1bf0497acae99e229468cf0add072d" + } + ], + "tmux": [ + "3.2a", + "3.7c" + ] + }, + { + "port": "dotnet", + "language": "csharp", + "sourceRevision": "320dc64f4b8b7815842471327a5e6b84a1499bf8", + "sourceRepository": "libtmux/libtmux-dotnet", + "toolchain": [ + ".NET SDK 10.0.302" + ], + "page": "ports/dotnet/guides/attaching-to-tmux", + "shellRecipe": [ + "git clone https://github.com/libtmux/libtmux-dotnet libtmux-source &&\n git -C libtmux-source checkout 320dc64f4b8b7815842471327a5e6b84a1499bf8 &&\n dotnet build Connect.csproj --maxcpucount:1 &&\n sh run.sh dotnet run --project Connect.csproj --no-build" + ], + "expectedOutput": "work", + "files": [ + { + "name": "Program.cs", + "sha256": "0936735d8c29c384b1040d37eca077690017d23eb6c21b9b6de489dde595f072" + }, + { + "name": "Connect.csproj", + "sha256": "badc813042195a1ac170ad3952437994fe7a85425b09b888f3e1e20f6fa71d15" + }, + { + "name": "run.sh", + "sha256": "bb6c3ecf7e120beacaee494336835da637c8f2a97c2881a75654eb1d494fef9e" + } + ], + "tmux": [ + "3.2a", + "3.7c" + ] + }, + { + "port": "cxx", + "language": "cpp", + "sourceRevision": "393d4b0ad666f18a6581f1eb281741a75a7503f0", + "sourceRepository": "libtmux/libtmux-cxx", + "toolchain": [ + "Clang 18", + "libc++ 18", + "CMake 3.25+" + ], + "page": "ports/cxx/guides/attaching-to-tmux", + "shellRecipe": [ + "git clone https://github.com/libtmux/libtmux-cxx libtmux-source &&\n git -C libtmux-source checkout 393d4b0ad666f18a6581f1eb281741a75a7503f0 &&\n cmake -S . -B build -G Ninja \\\n -DCMAKE_CXX_COMPILER=clang++ \\\n -DCMAKE_CXX_FLAGS=-stdlib=libc++ \\\n -DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++ &&\n cmake --build build --target connect --parallel 2 &&\n sh run.sh ./build/connect" + ], + "expectedOutput": "work", + "files": [ + { + "name": "connect.cpp", + "sha256": "e7cf8fedee0d797982c249ed194885c23c5c8f6d29e583592105dd09a191f875" + }, + { + "name": "CMakeLists.txt", + "sha256": "b21ea46308497a8fb5b6c7acda8c915a568da7f1573ae08aed67b379a217713a" + }, + { + "name": "run.sh", + "sha256": "e2d5c1be7459ab5be9f163902b6be34abfd8195c2d5ac003cb3342d178a829a4" + } + ], + "tmux": [ + "3.2a", + "3.7c" + ] + }, + { + "port": "swift", + "language": "swift", + "sourceRevision": "254f8b2be7eb60cacc3ffcb3ea8e456784f582df", + "sourceRepository": "libtmux/libtmux-swift", + "toolchain": [ + "Swift 6.2.4" + ], + "page": "ports/swift/guides/attaching-to-tmux", + "shellRecipe": [ + "git clone https://github.com/libtmux/libtmux-swift libtmux-source &&\n git -C libtmux-source checkout 254f8b2be7eb60cacc3ffcb3ea8e456784f582df &&\n sh run.sh swift run --jobs 2 Connect" + ], + "expectedOutput": "work", + "files": [ + { + "name": "Connect.swift", + "path": "Sources/Connect/Connect.swift", + "sha256": "9821b15aa0bb09ab626164f6f6a1e900f7263fafc5ff3d6297520505c8ebdc07" + }, + { + "name": "Package.swift", + "sha256": "bc9db4a88cdd0236c4985176f03a46c9dfac4f2401312149876d0fc013a91440" + }, + { + "name": "run.sh", + "sha256": "71f88925278d500b86c6571b92063ecf723275e8983468f6429d9fe2f6b2fe56" + } + ], + "tmux": [ + "3.2a", + "3.7c" + ] + }, + { + "port": "kotlin", + "language": "kotlin", + "sourceRevision": "85ebf6955e56703c5be74e2afd34a18309044741", + "sourceRepository": "libtmux/libtmux-java", + "toolchain": [ + "JDK 25.0.3", + "Kotlin 2.4.10" + ], + "page": "ports/kotlin/guides/attaching-to-tmux", + "shellRecipe": [ + "git clone https://github.com/libtmux/libtmux-java libtmux-source &&\n git -C libtmux-source checkout 85ebf6955e56703c5be74e2afd34a18309044741 &&\n sh run.sh ./libtmux-source/gradlew --project-dir . run --console=plain --max-workers=2" + ], + "expectedOutput": "work", + "files": [ + { + "name": "Connect.kt", + "sha256": "f908e010372c9a9391c6e20748bcdf296cce991b3822fe3f83f7016ec39a8671" + }, + { + "name": "build.gradle.kts", + "sha256": "e1d0abe83d5ccb4948dc48e4c035b1f60ef45a04924f25f4711a81dded646d27" + }, + { + "name": "settings.gradle.kts", + "sha256": "b00ae8397b15423ec37743ffe34966e3123fa2a21996d36457953dff131d60bc" + }, + { + "name": "gradle.properties", + "sha256": "aa2f75584718e340a2dab7af0d04d010cd8d0b83fc6b16f96a141cd3244821c1" + }, + { + "name": "run.sh", + "sha256": "a6a762cba4ee9405dbd633dba800fcd0824c82587daed9bb922b2b400d282f2c" + } + ], + "tmux": [ + "3.2a", + "3.7c" + ] + }, + { + "port": "scala", + "language": "scala", + "sourceRevision": "85ebf6955e56703c5be74e2afd34a18309044741", + "sourceRepository": "libtmux/libtmux-java", + "toolchain": [ + "JDK 25.0.3", + "Scala 3.9.0" + ], + "page": "ports/scala/guides/attaching-to-tmux", + "shellRecipe": [ + "git clone https://github.com/libtmux/libtmux-java libtmux-source &&\n git -C libtmux-source checkout 85ebf6955e56703c5be74e2afd34a18309044741 &&\n sh run.sh ./libtmux-source/gradlew --project-dir . run --console=plain --max-workers=2" + ], + "expectedOutput": "work", + "files": [ + { + "name": "Connect.scala", + "sha256": "cbca6ab2fdb96a5b4291cdc9003a17d32a098c2b5f7cff159b03bd4ab63ca1a5" + }, + { + "name": "build.gradle.kts", + "sha256": "745dadde4e2fff287d2bb57ee8b2679d19921af02745857b9a9a0c62c1dc04e9" + }, + { + "name": "settings.gradle.kts", + "sha256": "84f6f8c1760984676ed15761a8838fba725ee18fee25a28b8cc3f94ad3e24b99" + }, + { + "name": "gradle.properties", + "sha256": "aa2f75584718e340a2dab7af0d04d010cd8d0b83fc6b16f96a141cd3244821c1" + }, + { + "name": "run.sh", + "sha256": "200098b228e8bff4cf7c60ccdb8d43c8222c95c52c20e0b69b23c1bbb6c8ea8f" + } + ], + "tmux": [ + "3.2a", + "3.7c" + ] + }, + { + "port": "fsharp", + "language": "fsharp", + "sourceRevision": "661287848a6cfb407f37114b25e8249a29f99e3f", + "sourceRepository": "libtmux/libtmux-dotnet", + "toolchain": [ + ".NET SDK 10.0.302" + ], + "page": "ports/fsharp/guides/attaching-to-tmux", + "shellRecipe": [ + "git clone https://github.com/libtmux/libtmux-dotnet libtmux-source &&\n git -C libtmux-source checkout 661287848a6cfb407f37114b25e8249a29f99e3f &&\n dotnet build Connect.fsproj --maxcpucount:1 \\\n -p:DisableImplicitLibraryPacksFolder=true \\\n -p:RestorePackagesPath=\"$PWD/.packages\" &&\n sh run.sh dotnet run --project Connect.fsproj --no-build" + ], + "expectedOutput": "work", + "files": [ + { + "name": "Program.fs", + "sha256": "22a62e8423aa21ecb88aba93bf1cbf0520aa84c968ce18bb0559122b314517bc" + }, + { + "name": "Connect.fsproj", + "sha256": "314bfb2bef111e8885728a0065943a59594721011da4ac6ef0275aebd8f14bcd" + }, + { + "name": "run.sh", + "sha256": "ecb16ad7a624faf847875e73372706fa988893af7177c46a7912ed9364de88da" + } + ], + "tmux": [ + "3.2a", + "3.7c" + ] + }, + { + "port": "ruby", + "language": "ruby", + "sourceRevision": "2599d45369515aaf2fd5793bfe71cdc20de641b6", + "sourceRepository": "libtmux/libtmux-ruby", + "toolchain": [ + "Ruby 4.0.7", + "Bundler" + ], + "page": "ports/ruby/guides/attaching-to-tmux", + "shellRecipe": [ + "git clone https://github.com/libtmux/libtmux-ruby libtmux-source &&\n git -C libtmux-source checkout 2599d45369515aaf2fd5793bfe71cdc20de641b6 &&\n bundle config set --local path vendor/bundle &&\n bundle install &&\n sh run.sh bundle exec ruby connect.rb" + ], + "expectedOutput": "work", + "files": [ + { + "name": "connect.rb", + "sha256": "b683160fe4caefea0e65c4670519a2bfaa6c4f16f402fc86c730a42df2943f5d" + }, + { + "name": "Gemfile", + "sha256": "c0139fb5c549a7f4ccf72cf4ebf921de0497dbe9ea9a2e7c5756504ed5bf2f7c" + }, + { + "name": "run.sh", + "sha256": "4d06c2acca46bb7ccf0a240924240c940ff6329bbd288376005b4a0e910dabf5" + } + ], + "tmux": [ + "3.2a", + "3.7c" + ] + }, + { + "port": "lua", + "language": "lua", + "sourceRevision": "5baa3f9b830ebdbc76fb50b5b3d7a5ad3f76d443", + "sourceRepository": "libtmux/libtmux-lua", + "toolchain": [ + "Lua 5.5.1", + "luv 1.52.1-0" + ], + "page": "ports/lua/guides/attaching-to-tmux", + "shellRecipe": [ + "git clone https://github.com/libtmux/libtmux-lua libtmux-source &&\n git -C libtmux-source checkout 5baa3f9b830ebdbc76fb50b5b3d7a5ad3f76d443 &&\n luarocks --tree ./rocks install luv 1.52.1-0 &&\n (cd libtmux-source &&\n luarocks --tree ../rocks make rockspecs/libtmux-scm-1.rockspec) &&\n eval \"$(luarocks --tree ./rocks path)\" &&\n sh run.sh lua connect.lua" + ], + "expectedOutput": "work", + "files": [ + { + "name": "connect.lua", + "sha256": "4941e3f804521ec54e8692d83f6f3f22a0badb56affe4c24232ea28973fed2d6" + }, + { + "name": "run.sh", + "sha256": "18b752e8b0c36ef899af7bb5d4454469b34e5a70f6cfd7939ef5efec13c931a1" + } + ], + "tmux": [ + "3.2a", + "3.7c" + ] + } + ], + "rootExample": { + "file": "connect.sh", + "sha256": "ed88696309ac613caa271424dc4e1f52d2a6f18e4c1d5fe1ae01c385bc3dab1c", + "shellRecipe": "sh connect.sh", + "tmux": [ + "3.2a", + "3.7c" + ], + "interactiveCommands": [ + "tmux -L libtmux-demo -f /dev/null new-session -A -s work", + "tmux -L libtmux-demo list-sessions", + "tmux -L libtmux-demo attach-session -t '=work'", + "tmux -L libtmux-demo kill-session -t '=work'" + ] + } +}