# Workspaces

Source: https://libtmux.org/en/tmux/concepts/workspaces/

> Build pane layouts with the object API or a workspace configuration file.

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](https://tmuxp.git-pull.com/) YAML or JSON.

## Building one imperatively

Create a window, split it into panes, apply a layout, and send each pane its
command:

```python
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}
```

```rust
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?;
```

```go
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
}
_ = terminal
return window.SelectLayout(ctx, tmux.SelectLayoutRequest{Layout: "main-vertical"})
```

```java
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);
```

```csharp
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"));
```

```cpp
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");
```

```swift
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](https://libtmux.org/en/tmux/concepts/transports/)
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](https://libtmux.org/en/py/latest/workspace/) for its configuration and CLI.

### TypeScript
`@libtmux/workspace` applies a workspace configuration through
[`applyWorkspace`](<https://libtmux.org/en/ts/latest/workspace/reference/builder-applyworkspace/>). Pass the server and a configuration containing
`session_name` and `windows`.

### Go
The [`workspace`](<https://libtmux.org/en/go/latest/reference/#workspace>) package loads tmuxp-shaped workspace configurations.
See [Workspace Manager](https://libtmux.org/en/go/latest/workspace/) for the supported fields and CLI.

### Rust
The `tmux-workspace` crate loads tmuxp-shaped configurations.
See [Workspace Manager](https://libtmux.org/en/rs/latest/workspace/) for the supported fields and CLI.

### Java
`libtmux-workspace` supports the tmuxp configuration fields needed to
describe a workspace. See [Workspace Manager](https://libtmux.org/en/java/latest/workspace/) for the supported
configuration and CLI.

### C#
[`LibTmux.Workspace`](<https://libtmux.org/en/csharp/latest/reference/#LibTmux.Workspace>) reads tmuxp YAML. See [Workspace Manager](https://libtmux.org/en/csharp/latest/workspace/)
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`](<https://libtmux.org/en/ts/latest/workspace/reference/builder-applyworkspace/>) applies a desired configuration. Applying the same
configuration again reuses its existing objects:

```ts
await applyWorkspace(server, {
  session_name: "api",
  windows: [
    { window_name: "editor", panes: ["vim", "git status"] },
    { window_name: "server", panes: [{ shell_command: "bun dev", focus: true }] },
  ],
});
```

```rust
use tmux_workspace::{Workspace, WorkspaceBuilder};

let workspace = Workspace::from_yaml(yaml_source)?;
let session = WorkspaceBuilder::new(&server).build(&workspace).await?;
```

```go
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())
```

```java
Workspace workspace = WorkspaceBuilder.parse(yaml);
Session session = WorkspaceBuilder.build(server, workspace);
```

```csharp
WorkspaceFile workspace = WorkspaceFile.Parse(yaml);
WorkspaceResult result = await new WorkspaceBuilder(server).BuildAsync(workspace, ct);
```

```swift
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/`](<https://github.com/libtmux/libtmux-cxx/tree/3f93b021cb51e8349bfb9cecc6fd4091f6e79a0f/examples/workspace>) that reads tmuxp
configuration. The workspace builder is part of that example, rather than a
library package:

```cpp
// 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`](<https://libtmux.org/en/py/latest/reference/libtmux-window/>) and [`Session`](<https://libtmux.org/en/py/latest/reference/libtmux-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:

```python
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
```

```go
// 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))
}()
```

```csharp
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 block
```

See [Context managers](https://libtmux.org/en/tmux/topics/context-managers/) for cleanup support in each
port. Use explicit kill methods when the handle does not provide scope-based
cleanup.
