Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions extensions/cli/src/ui/TextBuffer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,12 +526,31 @@ describe("TextBuffer", () => {
buffer.handleInput(largeChunk, { ctrl: false, meta: false } as any);

// Small typed input should not be mixed into paste
const smallChunk = "abc";
buffer.handleInput(smallChunk, { ctrl: false, meta: false } as any);
expect(buffer.text).toBe(""); // Both chunks buffered

buffer.flushPendingInput();
expect(buffer.text).toBe(largeChunk + smallChunk);
});

it("should allow typing after paste accumulation delay", () => {
vi.useFakeTimers();

const largeChunk = "x".repeat(100);
buffer.handleInput(largeChunk, { ctrl: false, meta: false } as any);

// Advance time to simulate user typing after a delay
vi.advanceTimersByTime(60); // so that it is treated as typing

const typedChar = "a";
buffer.handleInput(typedChar, { ctrl: false, meta: false } as any);
expect(buffer.text).toBe("a");
expect(buffer.text).toBe("a"); // Typed char inserted immediately

buffer.flushPendingInput();
vi.advanceTimersByTime(250); // Finalize paste
expect(buffer.text).toBe("a" + largeChunk);

vi.useRealTimers();
});

it("should preserve all chunks when expanding (content loss fix)", () => {
Expand Down
32 changes: 15 additions & 17 deletions extensions/cli/src/ui/TextBuffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,26 +337,24 @@ export class TextBuffer {
const timeSinceLastInput = now - this._lastInputTime;

// If we're already in rapid input mode and this comes quickly, add to buffer
// But only if it's a reasonably large chunk - small inputs are likely typing, not paste
// This prevents typed characters from being mixed into paste content during accumulation
if (
this._rapidInputBuffer.length > 0 &&
timeSinceLastInput < 200 &&
input.length >= 50
) {
this._rapidInputBuffer += input;
this._lastInputTime = now;
if (this._rapidInputBuffer.length > 0 && timeSinceLastInput < 200) {
const isLikelyTyping = input.length < 50 && timeSinceLastInput >= 50;

if (this._rapidInputTimer) {
clearTimeout(this._rapidInputTimer);
}
if (!isLikelyTyping) {
this._rapidInputBuffer += input;
this._lastInputTime = now;

// Reset timer: 200ms pause indicates end of paste
this._rapidInputTimer = setTimeout(() => {
this.finalizeRapidInput();
}, 200);
if (this._rapidInputTimer) {
clearTimeout(this._rapidInputTimer);
}

return true;
// Reset timer: 200ms pause indicates end of paste
this._rapidInputTimer = setTimeout(() => {
this.finalizeRapidInput();
}, 200);

return true;
}
}

// Fallback paste detection: some terminals send large pastes as rapid chunks
Expand Down
Loading