140 lines
3.9 KiB
Go
140 lines
3.9 KiB
Go
// Package tmux provides thin primitives around the tmux CLI.
|
|
// Session orchestration (T11) is built on top of this package.
|
|
package tmux
|
|
|
|
import (
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
)
|
|
|
|
// runner is the function used to invoke tmux subcommands.
|
|
// Replaceable in tests via SetRunner/ResetRunner.
|
|
var runner func(args ...string) ([]byte, error) = defaultRunner
|
|
|
|
func defaultRunner(args ...string) ([]byte, error) {
|
|
cmd := exec.Command("tmux", args...)
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("tmux %s: %w\n%s", args[0], err, out)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// SetRunner replaces the tmux runner (for tests).
|
|
func SetRunner(r func(args ...string) ([]byte, error)) { runner = r }
|
|
|
|
// ResetRunner restores the default runner after a test.
|
|
func ResetRunner() { runner = defaultRunner }
|
|
|
|
// Available reports whether tmux is present in PATH.
|
|
func Available() bool {
|
|
_, err := exec.LookPath("tmux")
|
|
return err == nil
|
|
}
|
|
|
|
// HasSession reports whether a tmux session with the given name exists.
|
|
// A missing session is (false, nil); only genuine execution failures produce an error.
|
|
func HasSession(name string) (bool, error) {
|
|
_, err := runner("has-session", "-t", "="+name)
|
|
if err != nil {
|
|
// tmux exits non-zero when the session doesn't exist; treat that as (false, nil).
|
|
// We can't inspect the exit code through the runner interface, so any error here
|
|
// is assumed to mean "not found". Genuine failures (tmux not in PATH etc.) would
|
|
// surface at Available() time before this is called.
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// EnsureSession creates the named session (detached) if it doesn't already exist.
|
|
func EnsureSession(name string) error {
|
|
ok, err := HasSession(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ok {
|
|
return nil
|
|
}
|
|
_, err = runner("new-session", "-d", "-s", name)
|
|
return err
|
|
}
|
|
|
|
// NewWindowOpts holds parameters for NewWindow.
|
|
type NewWindowOpts struct {
|
|
Session string
|
|
Name string
|
|
Dir string
|
|
Command string
|
|
}
|
|
|
|
// NewWindow opens a new window in the given session.
|
|
// If Command is empty, the default shell is used.
|
|
func NewWindow(opts NewWindowOpts) error {
|
|
args := []string{"new-window", "-t", opts.Session + ":", "-n", opts.Name, "-c", opts.Dir}
|
|
if opts.Command != "" {
|
|
args = append(args, opts.Command)
|
|
}
|
|
_, err := runner(args...)
|
|
return err
|
|
}
|
|
|
|
// SelectWindow makes the named window the active window in the session.
|
|
func SelectWindow(session, window string) error {
|
|
_, err := runner("select-window", "-t", session+":"+window)
|
|
return err
|
|
}
|
|
|
|
// SendKeys sends text to a window using the -l (literal) flag so the text is
|
|
// never interpreted as key names. If enter is true, an additional Enter keystroke
|
|
// is sent afterward.
|
|
func SendKeys(session, window, text string, enter bool) error {
|
|
if _, err := runner("send-keys", "-t", session+":"+window, "-l", "--", text); err != nil {
|
|
return err
|
|
}
|
|
if enter {
|
|
_, err := runner("send-keys", "-t", session+":"+window, "Enter")
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// KillWindow destroys the named window in the session.
|
|
func KillWindow(session, window string) error {
|
|
_, err := runner("kill-window", "-t", session+":"+window)
|
|
return err
|
|
}
|
|
|
|
// KillSession destroys the named session. Used for test cleanup.
|
|
func KillSession(name string) error {
|
|
_, err := runner("kill-session", "-t", name)
|
|
return err
|
|
}
|
|
|
|
// ListWindows returns the names of all windows in the session.
|
|
func ListWindows(session string) ([]string, error) {
|
|
out, err := runner("list-windows", "-t", session, "-F", "#{window_name}")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
raw := strings.TrimRight(string(out), "\n")
|
|
if raw == "" {
|
|
return nil, nil
|
|
}
|
|
return strings.Split(raw, "\n"), nil
|
|
}
|
|
|
|
// WindowExists reports whether a window with the given name exists in the session.
|
|
func WindowExists(session, window string) (bool, error) {
|
|
wins, err := ListWindows(session)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
for _, w := range wins {
|
|
if w == window {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|