diff --git a/internal/sshserver/server.go b/internal/sshserver/server.go index 3fdc7e6..7ec67ab 100644 --- a/internal/sshserver/server.go +++ b/internal/sshserver/server.go @@ -447,17 +447,36 @@ type termSize struct { width int height int updated time.Time + timer *time.Timer } func (t *termSize) set(w, h, maxDimension, maxCells int, force bool) { + width, height := clampTermSize(w, h, maxDimension, maxCells, terminalSizeQuantum) + t.mu.Lock() - if !force && time.Since(t.updated) < resizeDebounce { - t.mu.Unlock() - return + defer t.mu.Unlock() + + if !force { + if remaining := resizeDebounce - time.Since(t.updated); remaining > 0 { + if t.timer != nil { + t.timer.Stop() + } + t.timer = time.AfterFunc(remaining, func() { + t.mu.Lock() + defer t.mu.Unlock() + t.width, t.height = width, height + t.updated = time.Now() + }) + return + } } - t.width, t.height = clampTermSize(w, h, maxDimension, maxCells, terminalSizeQuantum) + + if t.timer != nil { + t.timer.Stop() + t.timer = nil + } + t.width, t.height = width, height t.updated = time.Now() - t.mu.Unlock() } func (t *termSize) get() (int, int) { diff --git a/internal/sshserver/server_test.go b/internal/sshserver/server_test.go index 7bb367f..9d3829f 100644 --- a/internal/sshserver/server_test.go +++ b/internal/sshserver/server_test.go @@ -3,6 +3,7 @@ package sshserver import ( "sync" "testing" + "time" "golang.org/x/crypto/ssh" ) @@ -102,6 +103,26 @@ func TestTermSizeDebouncesResize(t *testing.T) { } } +func TestTermSizeAppliesFinalResizeAfterDebounce(t *testing.T) { + size := &termSize{} + size.set(80, 24, 512, 500*512, true) + + // Rapid burst of resize events, as happens during an interactive drag-resize. + size.set(100, 40, 512, 500*512, false) + size.set(150, 60, 512, 500*512, false) + size.set(200, 100, 512, 500*512, false) + + if w, h := size.get(); w != 80 || h != 24 { + t.Fatalf("size changed before debounce elapsed: %dx%d", w, h) + } + + time.Sleep(resizeDebounce + 50*time.Millisecond) + + if w, h := size.get(); w != 200 || h != 100 { + t.Fatalf("final resize was not applied after debounce: got %dx%d, want 200x100", w, h) + } +} + func TestClampTermSize(t *testing.T) { w, h := clampTermSize(1000, 500, 512, 65536, 4) if w < 1 || h < 1 || w > 512 || h > 512 || w*h > 65536 {