Fix tmux-compat split-window surface resolution (#2351)
* Add tmux-compat split-window ref regression tests * Fix tmux-compat split-window surface resolution * Fix stale tmux caller surface fallback * Add stale tmux-compat split-window regressions * Fix stale tmux-compat split-window anchors * Preserve tmux fallback and column anchor
This commit is contained in:
parent
867c93e4fa
commit
2c5c4fcf8d
4 changed files with 870 additions and 68 deletions
|
|
@ -383,6 +383,18 @@ func tmuxCallerSurfaceHandle() string {
|
|||
return strings.TrimSpace(os.Getenv("CMUX_SURFACE_ID"))
|
||||
}
|
||||
|
||||
func tmuxResolvedCallerWorkspaceId(rc *rpcContext) string {
|
||||
caller := tmuxCallerWorkspaceHandle()
|
||||
if caller == "" {
|
||||
return ""
|
||||
}
|
||||
wsId, err := tmuxResolveWorkspaceId(rc, caller)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return wsId
|
||||
}
|
||||
|
||||
func tmuxCallerPaneHandle() string {
|
||||
for _, key := range []string{"TMUX_PANE", "CMUX_PANE_ID"} {
|
||||
v := strings.TrimSpace(os.Getenv(key))
|
||||
|
|
@ -570,6 +582,29 @@ func tmuxCanonicalPaneId(rc *rpcContext, handle string, workspaceId string) (str
|
|||
return "", fmt.Errorf("pane not found: %s", handle)
|
||||
}
|
||||
|
||||
func tmuxCanonicalSurfaceId(rc *rpcContext, handle string, workspaceId string) (string, error) {
|
||||
payload, err := rc.call("surface.list", map[string]any{"workspace_id": workspaceId})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
surfaces, _ := payload["surfaces"].([]any)
|
||||
for _, s := range surfaces {
|
||||
surface, _ := s.(map[string]any)
|
||||
if surface == nil {
|
||||
continue
|
||||
}
|
||||
if ref, _ := surface["ref"].(string); ref == handle {
|
||||
if id, _ := surface["id"].(string); id != "" {
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
if id, _ := surface["id"].(string); id == handle {
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("surface not found: %s", handle)
|
||||
}
|
||||
|
||||
func tmuxFocusedPaneId(rc *rpcContext, workspaceId string) (string, error) {
|
||||
payload, err := rc.call("surface.current", map[string]any{"workspace_id": workspaceId})
|
||||
if err != nil {
|
||||
|
|
@ -648,7 +683,7 @@ func tmuxResolvePaneTarget(rc *rpcContext, raw string) (workspaceId string, pane
|
|||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
} else if callerWs := tmuxCallerWorkspaceHandle(); callerWs == workspaceId {
|
||||
} else if callerWs := tmuxResolvedCallerWorkspaceId(rc); callerWs == workspaceId {
|
||||
if callerPane := tmuxCallerPaneHandle(); callerPane != "" {
|
||||
if pid, err2 := tmuxCanonicalPaneId(rc, callerPane, workspaceId); err2 == nil {
|
||||
paneId = pid
|
||||
|
|
@ -707,8 +742,10 @@ func tmuxResolveSurfaceTarget(rc *rpcContext, raw string) (workspaceId string, p
|
|||
if callerPane != "" && callerSurface != "" {
|
||||
canonicalCallerPane, _ := tmuxCanonicalPaneId(rc, callerPane, workspaceId)
|
||||
if paneId == callerPane || paneId == canonicalCallerPane {
|
||||
surfaceId = callerSurface
|
||||
return
|
||||
surfaceId, err = tmuxCanonicalSurfaceId(rc, callerSurface, workspaceId)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
surfaceId, err = tmuxSelectedSurfaceId(rc, workspaceId, paneId)
|
||||
|
|
@ -723,10 +760,12 @@ func tmuxResolveSurfaceTarget(rc *rpcContext, raw string) (workspaceId string, p
|
|||
|
||||
// When no explicit target and caller workspace matches, use caller's surface
|
||||
if winSel == "" {
|
||||
if callerWs := tmuxCallerWorkspaceHandle(); callerWs == workspaceId {
|
||||
if callerWs := tmuxResolvedCallerWorkspaceId(rc); callerWs == workspaceId {
|
||||
if callerSurface := tmuxCallerSurfaceHandle(); callerSurface != "" {
|
||||
surfaceId = callerSurface
|
||||
return
|
||||
surfaceId, err = tmuxCanonicalSurfaceId(rc, callerSurface, workspaceId)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -769,6 +808,58 @@ func tmuxResolveSurfaceTarget(rc *rpcContext, raw string) (workspaceId string, p
|
|||
return "", "", "", fmt.Errorf("unable to resolve surface")
|
||||
}
|
||||
|
||||
type tmuxSplitAnchor struct {
|
||||
targetSurfaceId string
|
||||
callerSurfaceId string
|
||||
direction string
|
||||
}
|
||||
|
||||
func tmuxAnchoredSplitTarget(rc *rpcContext, workspaceId string) *tmuxSplitAnchor {
|
||||
store := loadTmuxCompatStore()
|
||||
if mvState, ok := store.MainVerticalLayouts[workspaceId]; ok && mvState.LastColumnSurfaceId != "" {
|
||||
lastColumnId, err := tmuxCanonicalSurfaceId(rc, mvState.LastColumnSurfaceId, workspaceId)
|
||||
if err == nil {
|
||||
return &tmuxSplitAnchor{
|
||||
targetSurfaceId: lastColumnId,
|
||||
callerSurfaceId: "",
|
||||
direction: "down",
|
||||
}
|
||||
}
|
||||
|
||||
// Right-column anchors can outlive the pane they pointed at.
|
||||
// Drop stale state and rebuild from the caller surface instead.
|
||||
mvState.LastColumnSurfaceId = ""
|
||||
store.MainVerticalLayouts[workspaceId] = mvState
|
||||
delete(store.LastSplitSurface, workspaceId)
|
||||
_ = saveTmuxCompatStore(store)
|
||||
}
|
||||
|
||||
candidateAnchors := []string{tmuxCallerSurfaceHandle()}
|
||||
if mvState, ok := store.MainVerticalLayouts[workspaceId]; ok && mvState.MainSurfaceId != "" {
|
||||
candidateAnchors = append(candidateAnchors, mvState.MainSurfaceId)
|
||||
}
|
||||
for _, candidate := range candidateAnchors {
|
||||
if candidate == "" {
|
||||
continue
|
||||
}
|
||||
anchorSurfaceId, err := tmuxCanonicalSurfaceId(rc, candidate, workspaceId)
|
||||
if err == nil {
|
||||
return &tmuxSplitAnchor{
|
||||
targetSurfaceId: anchorSurfaceId,
|
||||
callerSurfaceId: anchorSurfaceId,
|
||||
direction: "right",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := store.MainVerticalLayouts[workspaceId]; ok {
|
||||
delete(store.MainVerticalLayouts, workspaceId)
|
||||
delete(store.LastSplitSurface, workspaceId)
|
||||
_ = saveTmuxCompatStore(store)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- TmuxCompatStore (local JSON state) ---
|
||||
|
||||
type mainVerticalState struct {
|
||||
|
|
@ -777,10 +868,10 @@ type mainVerticalState struct {
|
|||
}
|
||||
|
||||
type tmuxCompatStore struct {
|
||||
Buffers map[string]string `json:"buffers,omitempty"`
|
||||
Hooks map[string]string `json:"hooks,omitempty"`
|
||||
MainVerticalLayouts map[string]mainVerticalState `json:"mainVerticalLayouts,omitempty"`
|
||||
LastSplitSurface map[string]string `json:"lastSplitSurface,omitempty"`
|
||||
Buffers map[string]string `json:"buffers,omitempty"`
|
||||
Hooks map[string]string `json:"hooks,omitempty"`
|
||||
MainVerticalLayouts map[string]mainVerticalState `json:"mainVerticalLayouts,omitempty"`
|
||||
LastSplitSurface map[string]string `json:"lastSplitSurface,omitempty"`
|
||||
}
|
||||
|
||||
func tmuxCompatStoreURL() string {
|
||||
|
|
@ -834,6 +925,47 @@ func saveTmuxCompatStore(store tmuxCompatStore) error {
|
|||
return os.WriteFile(path, data, 0644)
|
||||
}
|
||||
|
||||
func tmuxPruneCompatWorkspaceState(workspaceId string) error {
|
||||
store := loadTmuxCompatStore()
|
||||
changed := false
|
||||
if _, ok := store.MainVerticalLayouts[workspaceId]; ok {
|
||||
delete(store.MainVerticalLayouts, workspaceId)
|
||||
changed = true
|
||||
}
|
||||
if _, ok := store.LastSplitSurface[workspaceId]; ok {
|
||||
delete(store.LastSplitSurface, workspaceId)
|
||||
changed = true
|
||||
}
|
||||
if changed {
|
||||
return saveTmuxCompatStore(store)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tmuxPruneCompatSurfaceState(workspaceId string, surfaceId string) error {
|
||||
store := loadTmuxCompatStore()
|
||||
changed := false
|
||||
if lastSplit := store.LastSplitSurface[workspaceId]; lastSplit == surfaceId {
|
||||
delete(store.LastSplitSurface, workspaceId)
|
||||
changed = true
|
||||
}
|
||||
if layout, ok := store.MainVerticalLayouts[workspaceId]; ok {
|
||||
if layout.MainSurfaceId == surfaceId {
|
||||
delete(store.MainVerticalLayouts, workspaceId)
|
||||
delete(store.LastSplitSurface, workspaceId)
|
||||
changed = true
|
||||
} else if layout.LastColumnSurfaceId == surfaceId {
|
||||
layout.LastColumnSurfaceId = ""
|
||||
store.MainVerticalLayouts[workspaceId] = layout
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
return saveTmuxCompatStore(store)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- Special key translation ---
|
||||
|
||||
func tmuxSpecialKeyText(token string) string {
|
||||
|
|
@ -1069,20 +1201,16 @@ func tmuxSplitWindow(rc *rpcContext, args []string) error {
|
|||
direction = "up"
|
||||
}
|
||||
|
||||
// Anchor splits to the leader surface for agent teams
|
||||
callerSurface := tmuxCallerSurfaceHandle()
|
||||
// Anchor splits to the leader surface for agent teams.
|
||||
callerWorkspace := tmuxCallerWorkspaceHandle()
|
||||
if callerSurface != "" && callerWorkspace != "" {
|
||||
anchoredCallerSurface := ""
|
||||
if callerWorkspace != "" {
|
||||
if wsId, err := tmuxResolveWorkspaceId(rc, callerWorkspace); err == nil {
|
||||
store := loadTmuxCompatStore()
|
||||
if mvState, ok := store.MainVerticalLayouts[wsId]; ok && mvState.LastColumnSurfaceId != "" {
|
||||
if anchored := tmuxAnchoredSplitTarget(rc, wsId); anchored != nil {
|
||||
targetWs = wsId
|
||||
targetSurface = mvState.LastColumnSurfaceId
|
||||
direction = "down"
|
||||
} else {
|
||||
targetWs = wsId
|
||||
targetSurface = callerSurface
|
||||
direction = "right"
|
||||
targetSurface = anchored.targetSurfaceId
|
||||
direction = anchored.direction
|
||||
anchoredCallerSurface = anchored.callerSurfaceId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1110,9 +1238,9 @@ func tmuxSplitWindow(rc *rpcContext, args []string) error {
|
|||
mvs := store.MainVerticalLayouts[targetWs]
|
||||
mvs.LastColumnSurfaceId = surfaceId
|
||||
store.MainVerticalLayouts[targetWs] = mvs
|
||||
} else if direction == "right" && callerSurface != "" {
|
||||
} else if direction == "right" && anchoredCallerSurface != "" {
|
||||
store.MainVerticalLayouts[targetWs] = mainVerticalState{
|
||||
MainSurfaceId: callerSurface,
|
||||
MainSurfaceId: anchoredCallerSurface,
|
||||
LastColumnSurfaceId: surfaceId,
|
||||
}
|
||||
}
|
||||
|
|
@ -1178,7 +1306,11 @@ func tmuxKillWindow(rc *rpcContext, args []string) error {
|
|||
return err
|
||||
}
|
||||
_, err = rc.call("workspace.close", map[string]any{"workspace_id": wsId})
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = tmuxPruneCompatWorkspaceState(wsId)
|
||||
return nil
|
||||
}
|
||||
|
||||
func tmuxKillPane(rc *rpcContext, args []string) error {
|
||||
|
|
@ -1191,6 +1323,7 @@ func tmuxKillPane(rc *rpcContext, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = tmuxPruneCompatSurfaceState(wsId, surfId)
|
||||
// Re-equalize after removal
|
||||
rc.call("workspace.equalize_splits", map[string]any{"workspace_id": wsId, "orientation": "vertical"})
|
||||
return nil
|
||||
|
|
@ -1576,19 +1709,7 @@ func tmuxSelectLayout(rc *rpcContext, args []string) error {
|
|||
saveTmuxCompatStore(store)
|
||||
}
|
||||
} else if layoutName != "" {
|
||||
store := loadTmuxCompatStore()
|
||||
changed := false
|
||||
if _, ok := store.MainVerticalLayouts[wsId]; ok {
|
||||
delete(store.MainVerticalLayouts, wsId)
|
||||
changed = true
|
||||
}
|
||||
if _, ok := store.LastSplitSurface[wsId]; ok {
|
||||
delete(store.LastSplitSurface, wsId)
|
||||
changed = true
|
||||
}
|
||||
if changed {
|
||||
saveTmuxCompatStore(store)
|
||||
}
|
||||
_ = tmuxPruneCompatWorkspaceState(wsId)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
241
daemon/remote/cmd/cmuxd-remote/tmux_split_ref_test.go
Normal file
241
daemon/remote/cmd/cmuxd-remote/tmux_split_ref_test.go
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func startMockTmuxCompatSocket(t *testing.T) string {
|
||||
t.Helper()
|
||||
sockPath := makeShortUnixSocketPath(t)
|
||||
|
||||
ln, err := net.Listen("unix", sockPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { ln.Close() })
|
||||
|
||||
go func() {
|
||||
splitCreated := false
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
reader := bufio.NewReader(conn)
|
||||
line, err := reader.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var req map[string]any
|
||||
if err := json.Unmarshal(line, &req); err != nil {
|
||||
_, _ = conn.Write([]byte(`{"ok":false,"error":{"code":"parse","message":"bad json"}}` + "\n"))
|
||||
return
|
||||
}
|
||||
|
||||
method, _ := req["method"].(string)
|
||||
params, _ := req["params"].(map[string]any)
|
||||
resp := map[string]any{
|
||||
"id": req["id"],
|
||||
"ok": true,
|
||||
}
|
||||
|
||||
switch method {
|
||||
case "workspace.list":
|
||||
resp["result"] = map[string]any{
|
||||
"workspaces": []map[string]any{{
|
||||
"id": "11111111-1111-4111-8111-111111111111",
|
||||
"ref": "workspace:1",
|
||||
"index": 1,
|
||||
"title": "demo",
|
||||
}},
|
||||
}
|
||||
case "surface.list":
|
||||
surfaces := []map[string]any{{
|
||||
"id": "44444444-4444-4444-8444-444444444444",
|
||||
"ref": "surface:1",
|
||||
"focused": true,
|
||||
"pane_id": "33333333-3333-4333-8333-333333333333",
|
||||
"title": "leader",
|
||||
}}
|
||||
if splitCreated {
|
||||
surfaces = append(surfaces, map[string]any{
|
||||
"id": "77777777-7777-4777-8777-777777777777",
|
||||
"ref": "surface:2",
|
||||
"focused": false,
|
||||
"pane_id": "66666666-6666-4666-8666-666666666666",
|
||||
"title": "teammate",
|
||||
})
|
||||
}
|
||||
resp["result"] = map[string]any{"surfaces": surfaces}
|
||||
case "surface.current":
|
||||
resp["result"] = map[string]any{
|
||||
"workspace_id": "11111111-1111-4111-8111-111111111111",
|
||||
"workspace_ref": "workspace:1",
|
||||
"pane_id": "33333333-3333-4333-8333-333333333333",
|
||||
"pane_ref": "pane:1",
|
||||
"surface_id": "44444444-4444-4444-8444-444444444444",
|
||||
"surface_ref": "surface:1",
|
||||
}
|
||||
case "pane.list":
|
||||
panes := []map[string]any{{
|
||||
"id": "33333333-3333-4333-8333-333333333333",
|
||||
"ref": "pane:1",
|
||||
"index": 1,
|
||||
}}
|
||||
if splitCreated {
|
||||
panes = append(panes, map[string]any{
|
||||
"id": "66666666-6666-4666-8666-666666666666",
|
||||
"ref": "pane:2",
|
||||
"index": 2,
|
||||
})
|
||||
}
|
||||
resp["result"] = map[string]any{"panes": panes}
|
||||
case "surface.split":
|
||||
if got, _ := params["surface_id"].(string); got != "44444444-4444-4444-8444-444444444444" {
|
||||
resp["ok"] = false
|
||||
resp["error"] = map[string]any{
|
||||
"code": "not_found",
|
||||
"message": "Surface not found",
|
||||
}
|
||||
break
|
||||
}
|
||||
splitCreated = true
|
||||
resp["result"] = map[string]any{
|
||||
"surface_id": "77777777-7777-4777-8777-777777777777",
|
||||
"pane_id": "66666666-6666-4666-8666-666666666666",
|
||||
}
|
||||
case "workspace.equalize_splits":
|
||||
resp["result"] = map[string]any{"ok": true}
|
||||
default:
|
||||
resp["ok"] = false
|
||||
resp["error"] = map[string]any{
|
||||
"code": "unsupported",
|
||||
"message": method,
|
||||
}
|
||||
}
|
||||
|
||||
payload, _ := json.Marshal(resp)
|
||||
_, _ = conn.Write(append(payload, '\n'))
|
||||
}(conn)
|
||||
}
|
||||
}()
|
||||
|
||||
return sockPath
|
||||
}
|
||||
|
||||
func TestTmuxSplitWindowCanonicalizesCallerSurfaceRefs(t *testing.T) {
|
||||
origHome := os.Getenv("HOME")
|
||||
origWorkspace := os.Getenv("CMUX_WORKSPACE_ID")
|
||||
origSurface := os.Getenv("CMUX_SURFACE_ID")
|
||||
origPane := os.Getenv("TMUX_PANE")
|
||||
os.Setenv("HOME", t.TempDir())
|
||||
os.Setenv("CMUX_WORKSPACE_ID", "workspace:1")
|
||||
os.Setenv("CMUX_SURFACE_ID", "surface:1")
|
||||
os.Setenv("TMUX_PANE", "%pane:1")
|
||||
defer func() {
|
||||
os.Setenv("HOME", origHome)
|
||||
if origWorkspace != "" {
|
||||
os.Setenv("CMUX_WORKSPACE_ID", origWorkspace)
|
||||
} else {
|
||||
os.Unsetenv("CMUX_WORKSPACE_ID")
|
||||
}
|
||||
if origSurface != "" {
|
||||
os.Setenv("CMUX_SURFACE_ID", origSurface)
|
||||
} else {
|
||||
os.Unsetenv("CMUX_SURFACE_ID")
|
||||
}
|
||||
if origPane != "" {
|
||||
os.Setenv("TMUX_PANE", origPane)
|
||||
} else {
|
||||
os.Unsetenv("TMUX_PANE")
|
||||
}
|
||||
}()
|
||||
|
||||
sockPath := startMockTmuxCompatSocket(t)
|
||||
rc := &rpcContext{socketPath: sockPath}
|
||||
|
||||
output := captureStdout(t, func() {
|
||||
if err := dispatchTmuxCommand(rc, "split-window", []string{"-h", "-P", "-F", "#{pane_id}"}); err != nil {
|
||||
t.Fatalf("split-window: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if got := output; got != "%66666666-6666-4666-8666-666666666666\n" {
|
||||
t.Fatalf("stdout = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTmuxSplitWindowIgnoresStaleUUIDColumnSurface(t *testing.T) {
|
||||
origHome := os.Getenv("HOME")
|
||||
origWorkspace := os.Getenv("CMUX_WORKSPACE_ID")
|
||||
origSurface := os.Getenv("CMUX_SURFACE_ID")
|
||||
origPane := os.Getenv("TMUX_PANE")
|
||||
home := t.TempDir()
|
||||
os.Setenv("HOME", home)
|
||||
os.Setenv("CMUX_WORKSPACE_ID", "workspace:1")
|
||||
os.Setenv("CMUX_SURFACE_ID", "surface:1")
|
||||
os.Setenv("TMUX_PANE", "%pane:1")
|
||||
defer func() {
|
||||
os.Setenv("HOME", origHome)
|
||||
if origWorkspace != "" {
|
||||
os.Setenv("CMUX_WORKSPACE_ID", origWorkspace)
|
||||
} else {
|
||||
os.Unsetenv("CMUX_WORKSPACE_ID")
|
||||
}
|
||||
if origSurface != "" {
|
||||
os.Setenv("CMUX_SURFACE_ID", origSurface)
|
||||
} else {
|
||||
os.Unsetenv("CMUX_SURFACE_ID")
|
||||
}
|
||||
if origPane != "" {
|
||||
os.Setenv("TMUX_PANE", origPane)
|
||||
} else {
|
||||
os.Unsetenv("TMUX_PANE")
|
||||
}
|
||||
}()
|
||||
|
||||
storePath := filepath.Join(home, ".cmuxterm", "tmux-compat-store.json")
|
||||
if err := os.MkdirAll(filepath.Dir(storePath), 0o755); err != nil {
|
||||
t.Fatalf("mkdir store dir: %v", err)
|
||||
}
|
||||
storeBytes, err := json.Marshal(tmuxCompatStore{
|
||||
Buffers: make(map[string]string),
|
||||
Hooks: make(map[string]string),
|
||||
MainVerticalLayouts: map[string]mainVerticalState{
|
||||
"11111111-1111-4111-8111-111111111111": {
|
||||
MainSurfaceId: "44444444-4444-4444-8444-444444444444",
|
||||
LastColumnSurfaceId: "77777777-7777-4777-8777-777777777777",
|
||||
},
|
||||
},
|
||||
LastSplitSurface: map[string]string{
|
||||
"11111111-1111-4111-8111-111111111111": "77777777-7777-4777-8777-777777777777",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("marshal store: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(storePath, storeBytes, 0o644); err != nil {
|
||||
t.Fatalf("write store: %v", err)
|
||||
}
|
||||
|
||||
sockPath := startMockTmuxCompatSocket(t)
|
||||
rc := &rpcContext{socketPath: sockPath}
|
||||
|
||||
output := captureStdout(t, func() {
|
||||
if err := dispatchTmuxCommand(rc, "split-window", []string{"-h", "-P", "-F", "#{pane_id}"}); err != nil {
|
||||
t.Fatalf("split-window: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if got := output; got != "%66666666-6666-4666-8666-666666666666\n" {
|
||||
t.Fatalf("stdout = %q", got)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue