mirror of
https://github.com/wavetermdev/waveterm
synced 2026-05-24 09:18:27 +00:00
Adds `wsh attach` — a command that streams the live output of any Wave Terminal block to a local terminal window without affecting the remote session. Useful for monitoring long-running processes, CI jobs, or AI coding agents from a separate window or SSH session. Key capabilities: - Interactive block selector (workspace → tab → block) - Live PTY streaming via snapshot + WPS event subscription - Viewport model: server PTY size is fixed; local terminal is a moveable window into the remote screen (Ctrl+Arrow to pan) - Diff-based renderer that emits only changed cells per frame, with full SGR, wide-character, alt-screen, and cursor-style sync - Debounced render loop (16 ms) coalesces rapid PTY bursts so that full-screen TUI repaints are always consumed before rendering - Resync command (Ctrl-A s) rebuilds xterm-go state from a fresh snapshot when local state drifts from the remote Bug fix included: EventRecv messages are now dispatched synchronously in the WshRpc message loop (same pattern as StreamData/StreamDataAck) so that back-to-back PTY events are always processed in arrival order. Without this fix, concurrent goroutines race to write PTY chunks into the terminal emulator, producing mixed-frame garbling.
65 lines
1.4 KiB
Go
65 lines
1.4 KiB
Go
// Copyright 2026, Command Line Inc.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package waveattach
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestResolveDataDir_EnvOverride(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
t.Setenv("WAVETERM_DATA_HOME", tmp)
|
|
got, err := ResolveDataDir()
|
|
if err != nil {
|
|
t.Fatalf("unexpected err: %v", err)
|
|
}
|
|
if got != tmp {
|
|
t.Errorf("want %q, got %q", tmp, got)
|
|
}
|
|
}
|
|
|
|
func TestResolveDataDir_FallbackProd(t *testing.T) {
|
|
home := t.TempDir()
|
|
t.Setenv("HOME", home)
|
|
t.Setenv("WAVETERM_DATA_HOME", "")
|
|
prod := filepath.Join(home, ".waveterm")
|
|
if err := os.MkdirAll(prod, 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := ResolveDataDir()
|
|
if err != nil {
|
|
t.Fatalf("unexpected err: %v", err)
|
|
}
|
|
if got != prod {
|
|
t.Errorf("want %q, got %q", prod, got)
|
|
}
|
|
}
|
|
|
|
func TestResolveDataDir_FallbackDev(t *testing.T) {
|
|
home := t.TempDir()
|
|
t.Setenv("HOME", home)
|
|
t.Setenv("WAVETERM_DATA_HOME", "")
|
|
dev := filepath.Join(home, ".waveterm-dev")
|
|
if err := os.MkdirAll(dev, 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := ResolveDataDir()
|
|
if err != nil {
|
|
t.Fatalf("unexpected err: %v", err)
|
|
}
|
|
if got != dev {
|
|
t.Errorf("want %q, got %q", dev, got)
|
|
}
|
|
}
|
|
|
|
func TestResolveDataDir_NoneFound(t *testing.T) {
|
|
home := t.TempDir()
|
|
t.Setenv("HOME", home)
|
|
t.Setenv("WAVETERM_DATA_HOME", "")
|
|
if _, err := ResolveDataDir(); err == nil {
|
|
t.Fatal("expected error when no data dir exists")
|
|
}
|
|
}
|