Workspaces
A workspace arranges windows and panes for a task, such as editing code, running a development server, and following logs. Build it with the object API when the layout depends on program logic. Use a declarative builder when you want to store the layout in a configuration file, such as tmuxp YAML or JSON.
Building one imperatively¶
Create a window, split it into panes, apply a layout, and send each pane its command:
def create_dev_workspace(session, name='dev'): window = session.new_window(window_name=name, attach=False) window.resize(height=50, width=160)
main_pane = window.active_pane terminal_pane = main_pane.split(size='30%') log_pane = terminal_pane.split(direction=PaneDirection.Right)
return {'window': window, 'main': main_pane, 'terminal': terminal_pane, 'logs': log_pane}let mut window = session.new_window("dev").await?;let main_pane = window.active_pane().await?.expect("a new window has a pane");
let terminal_pane = main_pane .split(SplitOptions::new(SplitDirection::Below).size(PaneSize::Percent(30))) .await?;let logs_pane = terminal_pane .split(SplitOptions::new(SplitDirection::Right)) .await?;
window.select_layout(Layout::MainVertical).await?;window, err := session.NewWindow(ctx, tmux.NewWindowRequest{Name: tmux.Ptr("dev")})if err != nil { return err}// Attach: true makes the split active, so the next split divides it rather// than the pane that was already there.terminal, err := window.SplitPane(ctx, tmux.SplitPaneRequest{ Attach: true, Percentage: tmux.Ptr(30),})if err != nil { return err}if _, err := window.SplitPane(ctx, tmux.SplitPaneRequest{Direction: tmux.PaneDirectionRight}); err != nil { return err}_ = terminalreturn window.SelectLayout(ctx, tmux.SelectLayoutRequest{Layout: "main-vertical"})Window window = session.newWindow(w -> w.named("dev").detached());Pane terminal = window.split(split -> split.percent(30));Pane logs = terminal.split(split -> split.toRight());
window.selectLayout(Layout.MAIN_VERTICAL);Window window = await session.CreateWindowAsync(new NewWindowRequest(name: "dev"));Pane main = (await window.GetPanesAsync())[0];
Pane terminal = await main.SplitAsync(new SplitPaneRequest(percentage: 30));Pane logs = await terminal.SplitAsync(new SplitPaneRequest(direction: PaneDirection.Right));
await window.SelectLayoutAsync(new SelectLayoutRequest("main-vertical"));const auto window = session.new_window({.name = "dev"});if (!window.has_value()) return 1;
// `focus = true` makes the new pane active, so the next split divides it.const auto terminal = window->split({.percentage = 30, .focus = true});if (!terminal.has_value()) return 1;
const auto logs = window->split({.horizontal = true});if (!logs.has_value()) return 1;
(void)window->select_layout("main-vertical");let session = try await server.newSession(named: "work")let window = try await server.newWindow(in: session, named: "dev").window
let terminal = try await server.splitWindow(window, size: .percentage(30))let logs = try await server.split(terminal, direction: .right)
try await server.selectLayout(window, "main-vertical")A split creates a pane; direction and size control its placement. Applying a
layout rearranges existing panes while their processes continue running. tmux provides even-horizontal, even-vertical, main-horizontal,
main-vertical, and tiled layouts.
Choose detached creation when the user's current window should retain focus. Splits and resizes issue tmux commands; Control mode vs one-shot covers their transport costs and batching.
attach=False keeps a newly created window in the background.
Building one declaratively¶
These packages read or build workspace configurations based on tmuxp:
Python¶
tmuxp loads a workspace configuration and creates its sessions, windows,
and panes. See Workspace Manager for its configuration and CLI.
TypeScript¶
@libtmux/workspace applies a workspace configuration through
applyWorkspace. Pass the server and a configuration containing
session_name and windows.
Go¶
The workspace package loads tmuxp-shaped workspace configurations.
See Workspace Manager for the supported fields and CLI.
Rust¶
The tmux-workspace crate loads tmuxp-shaped configurations.
See Workspace Manager for the supported fields and CLI.
Java¶
libtmux-workspace supports the tmuxp configuration fields needed to
describe a workspace. See Workspace Manager for the supported
configuration and CLI.
C#¶
LibTmux.Workspace reads tmuxp YAML. See Workspace Manager
for configuration fields and the CLI.
Swift¶
TmuxWorkspace accepts configurations written in Swift, JSON, or YAML.
YAML support requires the YAMLWorkspaces trait.
TypeScript's applyWorkspace applies a desired configuration. Applying the same
configuration again reuses its existing objects:
await applyWorkspace(server, { session_name: "api", windows: [ { window_name: "editor", panes: ["vim", "git status"] }, { window_name: "server", panes: [{ shell_command: "bun dev", focus: true }] }, ],});use tmux_workspace::{Workspace, WorkspaceBuilder};
let workspace = Workspace::from_yaml(yaml_source)?;let session = WorkspaceBuilder::new(&server).build(&workspace).await?;described, err := workspace.Parse(document)if err != nil { return err}session, err := workspace.Build(ctx, server, described)if err != nil { return err}fmt.Println("workspace session:", session.ID())Workspace workspace = WorkspaceBuilder.parse(yaml);Session session = WorkspaceBuilder.build(server, workspace);WorkspaceFile workspace = WorkspaceFile.Parse(yaml);WorkspaceResult result = await new WorkspaceBuilder(server).BuildAsync(workspace, ct);let workspace = Workspace( sessionName: "work", windows: [WindowPlan(windowName: "editor", panes: [PanePlan(), PanePlan()])])let session = try await WorkspaceBuilder.build(workspace, on: server)C++ provides a consumer example in examples/workspace/ that reads tmuxp
configuration. The workspace builder is part of that example, rather than a
library package:
// Not a package: this is the examples/workspace/ consumer, showing the// shape a tmuxp document builds into rather than a library entry point.const workspace::Workspace description{ .session_name = "dev", .windows = {{.name = "editor", .panes = {{}, {}}}}};const auto built = workspace::build(server, description);Cleaning up¶
Window and Session context managers kill their objects on block exit,
including when the block raises:
Use a named error result in the enclosing function so deferred cleanup can
return its own failure. Give cleanup a fresh, bounded context:
An ownership scope kills its session when await using exits:
with session.new_window(window_name='temp-window') as temp_win: pane = temp_win.active_pane pane.send_keys('echo "temporary workspace"')# window is gone here, even if the block raised// No context manager: defer runs the cleanup at the end of the enclosing// function instead of the end of a block.session, err := server.NewSession(ctx, tmux.NewSessionRequest{Name: "temp-session"})if err != nil { return err}defer func() { cleanupCtx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() err = errors.Join(err, session.Kill(cleanupCtx))}()await using OwnedSessionScope scope = await server.CreateOwnedSessionAsync( new NewSessionRequest(name: "temp-session"));Window window = (await scope.Value.GetWindowsAsync())[0];Pane pane = (await window.GetPanesAsync())[0];
await pane.SendTextAsync("echo temporary workspace");// session is gone here, even if an exception unwound through the blockSee Context managers for cleanup support in each port. Use explicit kill methods when the handle does not provide scope-based cleanup.