package uv import ( "io" "reflect" "strings" "testing" "time" ) // Control strings split across reads, the way a large OSC 5522 clipboard // reply arrives over ssh. // stalledReader returns one chunk per Read, pausing before each like a slow // network link. type stalledReader struct { chunks []string pause time.Duration } func (r *stalledReader) Read(p []byte) (int, error) { if len(r.chunks) == 0 { return 0, io.EOF } time.Sleep(r.pause) n := copy(p, r.chunks[0]) r.chunks = r.chunks[1:] return n, nil } func TestDecodeWaitsForSplitStringTerminator(t *testing.T) { for name, seq := range map[string]string{ "osc": "\x1b]5522;type=read:status=DATA;QUJD\x1b", "apc": "\x1b_Gi=1;OK\x1b", "dcs": "\x1bP1+r5463=31\x1b", } { t.Run(name, func(t *testing.T) { var d EventDecoder n, ev := d.Decode([]byte(seq)) if _, ok := ev.(UnknownEvent); !ok || n != len(seq) { t.Fatalf("Decode(%q) = %d, %T; want an incomplete UnknownEvent over the whole buffer", seq, n, ev) } }) } } func TestStreamEventsKeepsOscAcrossStalledReads(t *testing.T) { payload := strings.Repeat("QUJD", 2000) osc := "\x1b]5522;type=read:status=DATA:id=x;" + payload + "\x1b\\" half := len(osc) / 2 chunks := []string{ osc[:half], // stall mid payload osc[half : len(osc)-1], // stall between ESC and the backslash of ST osc[len(osc)-1:] + "a", } events := testReadInputs(t, &stalledReader{chunks: chunks, pause: 4 * DefaultEscTimeout}) want := []Event{UnknownOscEvent(osc), KeyPressEvent{Code: 'a', Text: "a"}} if !reflect.DeepEqual(events, want) { t.Fatalf("events = %#v\nwant %#v", events, want) } } func TestStreamEventsFlushesStalledClipboardOscAfterItsTimeout(t *testing.T) { saved := pendingClipboardOscTimeout pendingClipboardOscTimeout = 2 * DefaultEscTimeout t.Cleanup(func() { pendingClipboardOscTimeout = saved }) head := "\x1b]5522;type=read:status=DATA:id=x;QUJD" events := testReadInputs(t, &stalledReader{chunks: []string{head, "a"}, pause: 6 * DefaultEscTimeout}) want := []Event{UnknownEvent(head), KeyPressEvent{Code: 'a', Text: "a"}} if !reflect.DeepEqual(events, want) { t.Fatalf("events = %#v\nwant %#v", events, want) } }