Skip to content

Commit cf5c1ab

Browse files
authored
Merge pull request #2837 from euxaristia/fix/quote-input-lag
fix(cli): remove quote-based drag detection to prevent input lag
2 parents c3bde0a + e6cb9d6 commit cf5c1ab

2 files changed

Lines changed: 31 additions & 105 deletions

File tree

packages/cli/src/ui/contexts/KeypressContext.test.tsx

Lines changed: 28 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1639,7 +1639,7 @@ describe('Drag and Drop Handling', () => {
16391639
});
16401640

16411641
describe('drag start by quotes', () => {
1642-
it('should start collecting when single quote arrives and not broadcast immediately', async () => {
1642+
it('should broadcast single quote immediately without lag', async () => {
16431643
const keyHandler = vi.fn();
16441644

16451645
const { result } = renderHook(() => useKeypressContext(), { wrapper });
@@ -1659,10 +1659,17 @@ describe('Drag and Drop Handling', () => {
16591659
});
16601660
});
16611661

1662-
expect(keyHandler).not.toHaveBeenCalled();
1662+
// Quote should be broadcast immediately without any delay
1663+
expect(keyHandler).toHaveBeenCalledTimes(1);
1664+
expect(keyHandler).toHaveBeenCalledWith(
1665+
expect.objectContaining({
1666+
sequence: SINGLE_QUOTE,
1667+
paste: false,
1668+
}),
1669+
);
16631670
});
16641671

1665-
it('should start collecting when double quote arrives and not broadcast immediately', async () => {
1672+
it('should broadcast double quote immediately without lag', async () => {
16661673
const keyHandler = vi.fn();
16671674

16681675
const { result } = renderHook(() => useKeypressContext(), { wrapper });
@@ -1682,12 +1689,19 @@ describe('Drag and Drop Handling', () => {
16821689
});
16831690
});
16841691

1685-
expect(keyHandler).not.toHaveBeenCalled();
1692+
// Quote should be broadcast immediately without any delay
1693+
expect(keyHandler).toHaveBeenCalledTimes(1);
1694+
expect(keyHandler).toHaveBeenCalledWith(
1695+
expect.objectContaining({
1696+
sequence: DOUBLE_QUOTE,
1697+
paste: false,
1698+
}),
1699+
);
16861700
});
16871701
});
16881702

16891703
describe('drag collection and completion', () => {
1690-
it('should collect single character inputs during drag mode', async () => {
1704+
it('should broadcast all characters immediately (no quote-based drag detection)', async () => {
16911705
const keyHandler = vi.fn();
16921706

16931707
const { result } = renderHook(() => useKeypressContext(), { wrapper });
@@ -1696,7 +1710,7 @@ describe('Drag and Drop Handling', () => {
16961710
result.current.subscribe(keyHandler);
16971711
});
16981712

1699-
// Start by single quote
1713+
// Send quote
17001714
act(() => {
17011715
stdin.pressKey({
17021716
name: undefined,
@@ -1708,58 +1722,20 @@ describe('Drag and Drop Handling', () => {
17081722
});
17091723
});
17101724

1711-
// Send single character
1712-
act(() => {
1713-
stdin.pressKey({
1714-
name: undefined,
1715-
ctrl: false,
1716-
meta: false,
1717-
shift: false,
1718-
paste: false,
1719-
sequence: 'a',
1720-
});
1721-
});
1722-
1723-
// Character should not be immediately broadcast
1724-
expect(keyHandler).not.toHaveBeenCalled();
1725-
1726-
// Fast-forward to completion timeout
1727-
act(() => {
1728-
vi.advanceTimersByTime(DRAG_COMPLETION_TIMEOUT_MS + 10);
1729-
});
1730-
1731-
// Should broadcast the collected path as paste (includes starting quote)
1732-
expect(keyHandler).toHaveBeenCalledWith(
1733-
expect.objectContaining({
1734-
name: '',
1735-
paste: true,
1736-
sequence: `${SINGLE_QUOTE}a`,
1737-
}),
1738-
);
1739-
});
1740-
1741-
it('should collect multiple characters and complete on timeout', async () => {
1742-
const keyHandler = vi.fn();
1743-
1744-
const { result } = renderHook(() => useKeypressContext(), { wrapper });
1745-
1746-
act(() => {
1747-
result.current.subscribe(keyHandler);
1748-
});
1725+
expect(keyHandler).toHaveBeenCalledTimes(1);
17491726

1750-
// Start by single quote
1727+
// Send path characters - all should be broadcast immediately
17511728
act(() => {
17521729
stdin.pressKey({
17531730
name: undefined,
17541731
ctrl: false,
17551732
meta: false,
17561733
shift: false,
17571734
paste: false,
1758-
sequence: SINGLE_QUOTE,
1735+
sequence: '/',
17591736
});
17601737
});
17611738

1762-
// Send multiple characters
17631739
act(() => {
17641740
stdin.pressKey({
17651741
name: undefined,
@@ -1804,22 +1780,16 @@ describe('Drag and Drop Handling', () => {
18041780
});
18051781
});
18061782

1807-
// Characters should not be immediately broadcast
1808-
expect(keyHandler).not.toHaveBeenCalled();
1783+
// All characters should be broadcast immediately
1784+
expect(keyHandler).toHaveBeenCalledTimes(6);
18091785

1810-
// Fast-forward to completion timeout
1786+
// Fast-forward timeout - should not trigger any additional broadcasts
18111787
act(() => {
18121788
vi.advanceTimersByTime(DRAG_COMPLETION_TIMEOUT_MS + 10);
18131789
});
18141790

1815-
// Should broadcast the collected path as paste (includes starting quote)
1816-
expect(keyHandler).toHaveBeenCalledWith(
1817-
expect.objectContaining({
1818-
name: '',
1819-
paste: true,
1820-
sequence: `${SINGLE_QUOTE}path`,
1821-
}),
1822-
);
1791+
// Still 6 broadcasts - no drag detection
1792+
expect(keyHandler).toHaveBeenCalledTimes(6);
18231793
});
18241794
});
18251795
});

packages/cli/src/ui/contexts/KeypressContext.tsx

Lines changed: 3 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,6 @@ export function KeypressProvider({
137137
}) {
138138
const { stdin, setRawMode } = useStdin();
139139
const subscribers = useRef<Set<KeypressHandler>>(new Set()).current;
140-
const isDraggingRef = useRef(false);
141-
const dragBufferRef = useRef('');
142-
const draggingTimerRef = useRef<NodeJS.Timeout | null>(null);
143140

144141
const subscribe = useCallback(
145142
(handler: KeypressHandler) => {
@@ -156,13 +153,6 @@ export function KeypressProvider({
156153
);
157154

158155
useEffect(() => {
159-
const clearDraggingTimer = () => {
160-
if (draggingTimerRef.current) {
161-
clearTimeout(draggingTimerRef.current);
162-
draggingTimerRef.current = null;
163-
}
164-
};
165-
166156
const wasRaw = stdin.isRaw;
167157
if (wasRaw === false) {
168158
setRawMode(true);
@@ -627,26 +617,9 @@ export function KeypressProvider({
627617
return;
628618
}
629619

630-
if (
631-
key.sequence === SINGLE_QUOTE ||
632-
key.sequence === DOUBLE_QUOTE ||
633-
isDraggingRef.current
634-
) {
635-
isDraggingRef.current = true;
636-
dragBufferRef.current += key.sequence;
637-
638-
clearDraggingTimer();
639-
draggingTimerRef.current = setTimeout(() => {
640-
isDraggingRef.current = false;
641-
const seq = dragBufferRef.current;
642-
dragBufferRef.current = '';
643-
if (seq) {
644-
broadcast({ ...key, name: '', paste: true, sequence: seq });
645-
}
646-
}, DRAG_COMPLETION_TIMEOUT_MS);
647-
648-
return;
649-
}
620+
// Note: We no longer treat quotes specially for drag-and-drop detection.
621+
// Modern terminals use bracketed paste mode (PASTE_MODE_PREFIX) for file drops,
622+
// which is handled above. This prevents input lag on quote keystrokes.
650623

651624
if (key.name === 'return' && waitingForEnterAfterBackslash) {
652625
if (backslashTimeout) {
@@ -1051,23 +1024,6 @@ export function KeypressProvider({
10511024
});
10521025
pasteBuffer = Buffer.alloc(0);
10531026
}
1054-
1055-
if (draggingTimerRef.current) {
1056-
clearTimeout(draggingTimerRef.current);
1057-
draggingTimerRef.current = null;
1058-
}
1059-
if (isDraggingRef.current && dragBufferRef.current) {
1060-
broadcast({
1061-
name: '',
1062-
ctrl: false,
1063-
meta: false,
1064-
shift: false,
1065-
paste: true,
1066-
sequence: dragBufferRef.current,
1067-
});
1068-
isDraggingRef.current = false;
1069-
dragBufferRef.current = '';
1070-
}
10711027
};
10721028
}, [
10731029
stdin,

0 commit comments

Comments
 (0)