From 595a4350e4a28bbfc3518d214ea04db9311910c5 Mon Sep 17 00:00:00 2001 From: Steven Miller Date: Thu, 29 Jan 2026 16:45:07 -0500 Subject: [PATCH] Handle terminal resizing --- pkg/cmd/exec.go | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/exec.go b/pkg/cmd/exec.go index ca8a2cd..5953de9 100644 --- a/pkg/cmd/exec.go +++ b/pkg/cmd/exec.go @@ -11,6 +11,7 @@ import ( "os" "os/signal" "strings" + "sync" "syscall" "github.com/gorilla/websocket" @@ -35,6 +36,8 @@ type execRequest struct { Env map[string]string `json:"env,omitempty"` Cwd string `json:"cwd,omitempty"` Timeout int32 `json:"timeout,omitempty"` + Rows uint32 `json:"rows,omitempty"` + Cols uint32 `json:"cols,omitempty"` } var execCmd = cli.Command{ @@ -127,6 +130,17 @@ func handleExec(ctx context.Context, cmd *cli.Command) error { execReq.Timeout = int32(timeout) } + // Get terminal size for TTY mode (only if stdout is actually a terminal) + if tty && term.IsTerminal(int(os.Stdout.Fd())) { + cols, rows, _ := term.GetSize(int(os.Stdout.Fd())) + if rows > 0 { + execReq.Rows = uint32(rows) + } + if cols > 0 { + execReq.Cols = uint32(cols) + } + } + reqBody, err := json.Marshal(execReq) if err != nil { return fmt.Errorf("failed to marshal request: %w", err) @@ -213,9 +227,30 @@ func runExecInteractive(ws *websocket.Conn) (int, error) { signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) defer signal.Stop(sigCh) + // Handle SIGWINCH for terminal resize + sigwinch := make(chan os.Signal, 1) + signal.Notify(sigwinch, syscall.SIGWINCH) + defer signal.Stop(sigwinch) + + // Mutex to protect WebSocket writes from concurrent access + var wsMu sync.Mutex + errCh := make(chan error, 2) exitCodeCh := make(chan int, 1) + // Handle terminal resize events + go func() { + for range sigwinch { + cols, rows, _ := term.GetSize(int(os.Stdout.Fd())) + if rows > 0 && cols > 0 { + msg := fmt.Sprintf(`{"resize":{"rows":%d,"cols":%d}}`, rows, cols) + wsMu.Lock() + ws.WriteMessage(websocket.TextMessage, []byte(msg)) + wsMu.Unlock() + } + } + }() + // Forward stdin to WebSocket go func() { buf := make([]byte, 32*1024) @@ -228,7 +263,10 @@ func runExecInteractive(ws *websocket.Conn) (int, error) { return } if n > 0 { - if err := ws.WriteMessage(websocket.BinaryMessage, buf[:n]); err != nil { + wsMu.Lock() + err := ws.WriteMessage(websocket.BinaryMessage, buf[:n]) + wsMu.Unlock() + if err != nil { errCh <- fmt.Errorf("websocket write error: %w", err) return }