workspace

package module
v0.0.1-alpha.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 22, 2026 License: MIT Imports: 11 Imported by: 0

README

workspace

Go Reference

Alpha software. Releases carry an -alpha prerelease tag and the API is not settled. Pin an exact version.

Load tmuxp-style YAML workspace files and build them with the tmux module.

This is a consumer of the tmux module, not part of it. The tmux module takes no runtime dependency; parsing YAML needs one, so this lives in its own module and go get on the tmux module never pulls it in.

$ go get github.com/libtmux/libtmux-go/workspace

Using it

import (
    "github.com/libtmux/libtmux-go/tmux"
    "github.com/libtmux/libtmux-go/workspace"
)
document, err := os.ReadFile("project.yaml")
if err != nil {
    return err
}
described, err := workspace.Parse(document)
if err != nil {
    return err
}
session, err := workspace.Build(ctx, tmux.NewServer(tmux.ServerOptions{}), described)

Parse rejects a field it does not recognise rather than dropping it, so a workspace that loads is a workspace that was understood. Validation reports every problem it finds at once, each with the line it is on, so a file is fixed in one pass rather than one run per mistake. It reports every parse and validation failure as ErrInvalidWorkspace; a failure tmux raises while building, such as an unknown layout or option name, is a tmux command error and is classified with the tmux package's own sentinels. Build uses strict errors regardless of the server it is handed, because a workspace that half exists is never what the caller wanted.

Build runs over a control connection, so a workspace costs a handful of tmux processes rather than one per command. That connection is a tmux client while the build runs: it shows in list-clients, counts toward session_attached, and fires a client-attached hook. Pass a server carrying SubprocessEngine() to build on processes instead.

Build is not atomic. tmux has no transaction, so a failure partway through leaves what it already created in place; the returned session identifies it, so you can kill it. Its relation accessors are empty, because it came from a creation call rather than a snapshot — take a Server.Snapshot to inspect what was built.

A start_directory that does not exist is not an error. tmux falls back to the user's home directory and reports success, so a workspace naming a directory that is absent on a given machine builds and puts every pane somewhere else. Workspace.MissingDirectories reports those before you build, for a caller who would rather say something than let it pass. It is not a validation failure, because a directory a shell_command_before creates is ordinary.

A workspace

session_name: project
start_directory: ~/src/project    # created beforehand; tmux falls back to $HOME if absent
environment:
  PROJECT_ENV: development
windows:
  - window_name: editor
    layout: main-vertical
    focus: true
    shell_command_before:
      - cd ~/src/project
    panes:
      - $EDITOR .
      - shell_command:
          - git status
          - cmd: make watch
            sleep_before: 1
  - window_name: shell
    panes:
      - {}

Supported fields

Scope Fields
workspace session_name, start_directory, environment, options, global_options, shell_command_before, suppress_history, windows
window window_name, window_index, layout, start_directory, window_shell, focus, suppress_history, options, options_after, environment, shell_command_before, panes
pane shell_command, shell_command_before, start_directory, shell, focus, suppress_history, environment, enter, sleep_before, sleep_after
command cmd, sleep_before, sleep_after, enter

A pane may be written as a bare command string, and shell_command and shell_command_before each accept one command or a list. Booleans accept tmuxp's quoted spellings, so focus: "true" and focus: true are the same wherever a boolean is taken. In Go they are the exported Bool type, so a workspace can be built in code as well as read from a file.

Of the 23 workspaces in tmuxp's own examples/ directory, 20 parse and all 20 of those build against real tmux. The three that do not parse use plugins or before_script, which need a Python runtime and are rejected rather than ignored.

Not supported

plugins loads Python classes and before_script runs a script through tmuxp's own process handling. Both need a Python runtime, so both are rejected rather than ignored.

Values are passed to tmux as written. tmuxp expands ${VAR} references before building; this module does not, so a workspace that depends on expansion should be rendered before it is parsed.

sleep_before and sleep_after are numbers of seconds, as in tmuxp, so 0.5 is half a second. They pause the build rather than the pane: the delay exists to let a previous command settle before the next is typed.

environment at any level is written to the session, because that is the only environment tmux keeps. A name set by two windows or two panes ends up holding whichever was written last, and every process started in that session afterwards sees it, including panes the user opens by hand.

options are applied to the session. A name from tmux's window table is accepted, as tmux accepts one there, and lands on the session's current window.

global_options are applied after the session exists, because tmux has no global scope until a server is running. The first window is created with the session and so cannot inherit them; every later window can. Any option tmux itself accepts under set-option -g is accepted here, whichever of its three option tables declares the name, so mode-keys and status-style may sit side by side as they do in a tmuxp file.

A window's first pane is created with the window, so window_shell and a shell on that pane name the same command. Either one runs it; setting both is rejected.

Documentation

Overview

Package workspace loads tmuxp-style YAML workspace files and builds them with the tmux package.

It is a consumer of the tmux module rather than part of it: the tmux module takes no runtime dependency, while parsing YAML needs one, so this lives in its own module. Compatibility with tmuxp is deliberate but partial. The fields below are the ones tmuxp's own examples use; anything else in a file is rejected rather than silently ignored, so a workspace that loads is a workspace that was understood.

tmuxp features that require a Python runtime are out of scope and stay rejected: plugins loads Python classes, and before_script runs an external script through tmuxp's own process handling.

Example

Load a tmuxp-style document and build the session it describes.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/libtmux/libtmux-go/tmux"
	"github.com/libtmux/libtmux-go/workspace"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()
	server := tmux.NewServer(tmux.ServerOptions{
		SocketName: "libtmux-go-example-workspace",
	})
	defer killExampleServer(server)

	document := []byte(`
session_name: review
windows:
  - window_name: editor
    panes:
      - shell_command: printf 'ready\n'
  - window_name: tests
    panes:
      - shell_command: printf 'ready\n'
      - shell_command: printf 'ready\n'
`)
	parsed, err := workspace.Parse(document)
	if err != nil {
		fmt.Println("parse:", err)
		return
	}

	session, err := workspace.Build(ctx, server, parsed)
	if err != nil {
		fmt.Println("build:", err)
		return
	}

	name, _ := session.Name()
	windows, err := session.SearchWindows(ctx, nil)
	if err != nil {
		fmt.Println("search windows:", err)
		return
	}
	fmt.Println(name, len(windows))
}

// killExampleServer stops an example's server on a context of its own, since an
// example's own context may already be spent by the time it returns.
func killExampleServer(server tmux.Server) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	_ = server.Kill(ctx)
}
Output:
review 2

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrInvalidWorkspace = errors.New("workspace: invalid workspace")

ErrInvalidWorkspace identifies a workspace file that could not be decoded or that Validate rejects. It is matched by errors.Is.

It does not cover a failure tmux reports while Build runs. tmux refusing a layout name or an option name is a command failure, so classify those with the tmux package's own errors rather than with this one.

Functions

func Build

func Build(ctx context.Context, server tmux.Server, workspace Workspace) (tmux.Session, error)

Build creates the workspace on server and returns the created session.

Build is not atomic. tmux has no transaction, so a failure partway through leaves the windows and panes created so far in place; the returned session identifies them so a caller can inspect or kill what exists. Build uses strict errors regardless of server's setting, because a workspace that half exists is never the caller's intent.

Build runs over a control connection, which carries a tmux command without starting a process for it and takes most of the cost out of a workspace. The connection is a tmux client for the length of the call: it appears in list-clients, counts toward session_attached, and fires a client-attached hook. Hand Build a server carrying tmux.Server.SubprocessEngine to decline it, which is what a tmux configuration that reacts to attachment wants.

A pane's commands are its workspace, window, and pane shell_command_before entries in that order, then its own shell_command entries. sleep_before and sleep_after pause Build rather than the pane, matching tmuxp: the delay exists to let a previous command settle before the next is typed.

Types

type Bool

type Bool bool

Bool is a YAML boolean that also accepts tmuxp's quoted spellings, because tmuxp workspaces in the wild write focus: "true" as often as focus: true.

It is exported so that every field holding one can be written in Go as well as read from a file: an optional setting is a *Bool, and a pointer to an unexported type is not something a caller outside this package can make.

func (*Bool) UnmarshalYAML

func (f *Bool) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML decodes a boolean written as a boolean or as a string.

type Command

type Command struct {
	// Command is the text typed into the pane.
	Command string `yaml:"cmd"`
	// SleepBefore delays this command without delaying the ones before it.
	// YAML writes it as a number of seconds, matching tmuxp, so 0.5 is half a
	// second; it is not a duration string, which is why it is decoded rather
	// than taken from this field.
	SleepBefore time.Duration `yaml:"-"`
	// SleepAfter delays the commands that follow this one, in the same units.
	SleepAfter time.Duration `yaml:"-"`
	// Enter reports whether to press Enter after the command. Nil presses it,
	// matching tmuxp, where omitting enter runs the command.
	Enter *Bool `yaml:"enter"`
}

Command is one command to run in a pane. YAML accepts either a bare string or a mapping, so "echo hi" and {cmd: echo hi, sleep_before: 2} are both commands.

func (*Command) UnmarshalYAML

func (c *Command) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML decodes a command written as a bare string or as a mapping.

type Pane

type Pane struct {
	// Commands run in the pane, in order.
	Commands []Command `yaml:"-"`
	// CommandsBefore run before Commands, after the window's.
	CommandsBefore []Command `yaml:"-"`
	// StartDirectory overrides the window directory for this pane.
	StartDirectory string `yaml:"start_directory"`
	// Shell replaces the command this pane runs. On a window's first pane it
	// is the window's command, because tmux creates that pane with the window;
	// setting it alongside window_shell is rejected rather than resolved.
	Shell string `yaml:"shell"`
	// Focus selects this pane once its window is built.
	Focus Bool `yaml:"focus"`
	// SuppressHistory overrides the window setting for this pane.
	SuppressHistory *Bool `yaml:"suppress_history"`
	// Environment is set on the session before this pane is created. It is the
	// session's environment rather than the pane's, so a name used by more
	// than one pane keeps the last value written, including for panes the user
	// opens afterwards.
	Environment map[string]string `yaml:"environment"`
	// Enter applies to every command in this pane unless the command sets its
	// own. Nil presses Enter, matching tmuxp.
	Enter *Bool `yaml:"enter"`
	// SleepBefore delays before each of this pane's commands. YAML writes it
	// as a number of seconds, matching tmuxp, so 0.5 is half a second.
	SleepBefore time.Duration `yaml:"-"`
	// SleepAfter delays after each of this pane's commands, in the same units.
	SleepAfter time.Duration `yaml:"-"`
}

Pane is one pane in a window. YAML accepts either a bare command string or a mapping, so "echo hello" and {shell_command: echo hello} are equivalent.

func (*Pane) UnmarshalYAML

func (p *Pane) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML decodes a pane written as a bare command or as a mapping.

type Window

type Window struct {
	// Name is the window name. Empty lets tmux choose.
	Name string `yaml:"window_name"`
	// Index requests an explicit winlink index. Nil uses the next free index.
	Index *int `yaml:"window_index"`
	// Layout is a tmux layout name applied after the panes exist.
	Layout string `yaml:"layout"`
	// StartDirectory overrides the workspace directory for this window.
	StartDirectory string `yaml:"start_directory"`
	// Shell replaces the command the window's first pane runs.
	Shell string `yaml:"window_shell"`
	// Focus selects this window once the workspace is built.
	Focus Bool `yaml:"focus"`
	// SuppressHistory overrides the workspace setting for this window's panes.
	SuppressHistory *Bool `yaml:"suppress_history"`
	// Options are window options applied before this window's panes are created.
	Options map[string]string `yaml:"options"`
	// OptionsAfter are window options applied once the panes exist, for settings
	// that a later split would otherwise overwrite.
	OptionsAfter map[string]string `yaml:"options_after"`
	// Environment is set on the session before this window is created. tmux
	// keeps one environment per session rather than one per window, so a name
	// used by more than one window keeps the last value written and every
	// process started later in that session sees it.
	Environment map[string]string `yaml:"environment"`
	// CommandsBefore run in each of this window's panes before the pane's own
	// commands, after the workspace's.
	CommandsBefore []Command `yaml:"-"`
	// Panes are created in order. The first pane is the window's initial pane;
	// each later one splits the window.
	Panes []Pane `yaml:"panes"`
	// contains filtered or unexported fields
}

Window is one window in a workspace.

func (*Window) UnmarshalYAML

func (w *Window) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML decodes the window, then its shell_command_before.

type Workspace

type Workspace struct {
	// SessionName names the created session. It is required.
	SessionName string `yaml:"session_name"`
	// StartDirectory is the default working directory for every window and pane
	// that does not set its own.
	StartDirectory string `yaml:"start_directory"`
	// Environment is set on the session before its windows are created.
	Environment map[string]string `yaml:"environment"`
	// Options are applied to the session after it exists. Keys are tmux option
	// names. A name from tmux's window table, such as main-pane-height, is
	// accepted here as tmux accepts it, and lands on the session's current
	// window the way tmux would put it there.
	Options map[string]string `yaml:"options"`
	// GlobalOptions are applied at tmux's global scope before the windows are
	// created, so a window can inherit them.
	GlobalOptions map[string]string `yaml:"global_options"`
	// CommandsBefore run in every pane before that pane's own commands. Window
	// and pane entries add to this list rather than replacing it.
	CommandsBefore []Command `yaml:"-"`
	// SuppressHistory prefixes commands with a space unless a window or pane
	// overrides it.
	SuppressHistory Bool `yaml:"suppress_history"`
	// Windows are created in order. A workspace needs at least one.
	Windows []Window `yaml:"windows"`
}

Workspace is one tmuxp-style session description.

func Parse

func Parse(document []byte) (Workspace, error)

Parse decodes one workspace document. Unknown fields are rejected so a misspelled key fails loudly instead of being dropped.

Example (UnknownField)

A misspelled key fails the parse rather than being dropped, so a workspace that does not do what its author meant says so before anything is built.

package main

import (
	"fmt"

	"github.com/libtmux/libtmux-go/workspace"
)

func main() {
	_, err := workspace.Parse([]byte("session_name: review\nwindow:\n  - {}\n"))
	fmt.Println(err != nil)
}
Output:
true

func (Workspace) MissingDirectories

func (w Workspace) MissingDirectories() []string

MissingDirectories reports the start directories the workspace names that do not exist, in the order they appear.

It is separate from Workspace.Validate because a missing directory is not necessarily a mistake: a workspace whose shell_command_before creates one is ordinary, and rejecting it would refuse a file that works. tmux does not report one either. It starts the pane in the user's home directory and reports success, so a workspace naming a directory absent on this machine builds and puts panes somewhere other than where its file says.

Call it before Build to turn that silence into something a caller can report. A name beginning with ~ is resolved against the current user's home directory, as tmux resolves it.

func (*Workspace) UnmarshalYAML

func (w *Workspace) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML decodes the workspace, then its shell_command_before, which accepts one command or a list.

func (Workspace) Validate

func (w Workspace) Validate() error

Validate reports whether the workspace describes something buildable.

Every problem it finds is reported, not the first, because fixing a file one complaint per run is the slowest way to learn what is wrong with it. The result matches ErrInvalidWorkspace however many problems it carries.

Directories

Path Synopsis
examples
build-workspace command
Command build-workspace loads a tmuxp-style YAML workspace and builds it, then reports what tmux actually created.
Command build-workspace loads a tmuxp-style YAML workspace and builds it, then reports what tmux actually created.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL