libtmux Reference MCP Search

Build a workspace from a file

Edit this page on GitHub

tmuxp popularized describing a tmux session as YAML — a session name, a list of windows, a list of panes and shell commands per window — and building it in one call instead of scripting each piece by hand. Several ports ship their own reader and builder for that same shape, on top of their object API rather than instead of it. Where a port has no checked snippet for this, the paragraph below says so instead of inventing one; see the table at the end for exactly which file backs each block that does exist.

Not every port folds this into the library itself. Python’s own README draws that line directly: tmuxp is “an app on top of libtmux” for exactly this job — “declarative tmux workspaces from YAML / TOML” — kept as a separate project rather than absorbed into the library. Depend on tmuxp there; it is built on the same Server/Session/Window/Pane objects the rest of this site documents.

import type { Server } from "libtmux/server";
import type { Session } from "libtmux/session";
import { applyWorkspace } from "@libtmux/workspace";
import type { WorkspaceInput } from "@libtmux/workspace/config";
/**
* Build the shape most people reach for tmux to get: one session, a window
* per concern, each pane already running the thing it is there for.
*
* `server.batch` plans several windows and resolves them from one final
* snapshot. `buildWorkspace`, below, delegates declared topology to the
* workspace package instead of maintaining another reconciler here.
*/
export async function buildSimpleWorkspace(server: Server): Promise<Session> {
const built = await server.newSession({
name: "work",
shellCommand: "sleep 30",
windowName: "editor",
});
const [logs, shell] = await server.batch([
built.plan.newWindow({ name: "logs", shellCommand: "tail -f /dev/null" }),
built.plan.newWindow({ name: "shell" }),
]);
await logs.selectLayout("even-horizontal");
void shell.name; // "shell"
return built;
}
export const DEVELOPMENT_WORKSPACE = {
session_name: "workspace-example",
windows: [
{ panes: ["sleep 30", "sleep 30"], window_name: "editor" },
{ panes: ["sleep 30"], window_name: "server" },
{ window_name: "logs" },
],
} satisfies WorkspaceInput;
/** Apply the package's declarative workspace to a server. */
export function buildWorkspace(server: Server): Promise<Session> {
return applyWorkspace(server, DEVELOPMENT_WORKSPACE);
}
/**
* Tear a workspace down without caring whether it is there.
*
* Killing a session that has already gone is not a failure worth propagating,
* which is the one case worth handling separately from every other tmux error.
*/
export async function removeWorkspace(server: Server, name: string): Promise<boolean> {
const found = (await server.snapshot()).sessions.first({ name });
if (found === undefined) return false;
await found.kill();
return true;
}

TypeScript’s applyWorkspace converges rather than duplicates when run twice against the same config — it describes a target state, not a one-shot script.

Go’s Example(), in workspace/example_test.go, is a Go Example function: go test runs it and checks its output against the // Output: comment at the end, so this is executed on every test run rather than merely present in a README. Parse rejects a field it doesn’t recognize rather than dropping it silently, and reports every problem it finds at once with the line it’s on. Build is not atomic: tmux has no transaction, so a failure partway through leaves whatever was already created in place, identified by the session Build still returns.

The tmux-workspace crate can also go the other direction — freeze(&session).await? turns a session someone built by hand back into the same YAML shape, recovering the topology (windows, panes, working directories) but not history: “tmux remembers what a pane is running, not the command someone typed to start it.”

Java’s read and parse produce a Workspace value; build is the only call that touches tmux — a description tmux couldn’t build is rejected while it’s still text, before a single window exists.

The .NET builder waits for a pane to look ready before sending workspace commands to it, rather than assuming a freshly spawned shell can already take input — see Sending keys for why that race is real. It polls pane_current_command, cursor_x, and cursor_y for up to ten seconds by default. Its default policy, PaneReadiness.Auto, waits only when the session’s default shell is zsh; PaneReadiness.Always waits before every pane running the session’s default shell; PaneReadiness.Never sends commands immediately. BuildAsync isn’t transactional either: its thrown WorkspaceBuildException carries a PartialResult with whatever was materialized before the failure.

No checked, public-API snippet exists for this task in C++. examples/workspace/ exists, but its own README frames it plainly as “not really an example. This is a consumer” — a project built to put weight on the public surface from outside and report where it’s awkward, reading tmuxp’s own YAML documents through types (workspace.hpp, tmuxp.hpp) that live only in that example directory, not in the library the way libtmux::testing does. Running its corpus probe against tmuxp’s real example files found two real gaps this way — environment: and window_index: were unsupported — both since fixed, and worth reading the example’s own README if you’re building this yourself against the core library’s typed API.

Swift’s WorkspaceBuilder.build refuses rather than adopting a session that already has the name — two callers building the same workspace shouldn’t silently share one. Workspace.decode(yaml:), behind the YAMLWorkspaces trait, reads the tmuxp-format YAML directly; Workspace.decode(json:) needs no trait, because tmuxp’s keys decode straight into these types.

Where this comes fromLink to section

PortSourceIn this pageChecked by
Pythonno fence; the README says tmuxp is a separate project by designn/a
TypeScriptexamples/workspace/workspace.ts (@libtmux/workspace)read whole from the filerun against real tmux by bun test examples/workspace
Goworkspace/example_test.go (workspace.Parse / workspace.Build)read whole from the fileExample() and its siblings run under go test and are checked against their own // Output: comments
Rustcrates/tmux-workspace/README.md, “Build it”hand-quotedthe crate’s own crates/tmux-workspace/src/lib.rs includes the README as a doc comment (#![doc = include_str!("../README.md")]), so cargo test --doc runs this exact block
Javalibtmux-workspace/README.md, “What you get back”hand-quotedevery Java fence in the module’s README is compiled and run against real tmux by docs-tests
.NETsrc/LibTmux.Workspace/README.mdhand-quotedone of the READMEs and docs ReadmeExampleTests compiles and runs against real tmux
C++examples/workspace/ (a consumer, not a library API)prose onlyexamples/workspace/tests/ runs it against real tmux as consumer.workspace; it exercises the example’s own types, not a published libtmux API
SwiftExamples/Sources/ExampleCode/Workspaces.swiftread whole from the filematched against the README’s “Workspaces, from a file or from Swift” section by Scripts/check_examples.py; compiled and run by swift test --package-path Examples

Rust, Java, and .NET quote a fenced block straight out of a README rather than a standalone example file — none of the three has one for this task — so those three rows are hand-quoted rather than read live: a build-time file= read of a whole README would pull in its surrounding prose along with the code.

Esc

Type to search.