mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-19 16:28:28 +00:00
fix(cli): ignore literal Tab input in BaseTextInput (#3270)
Some checks are pending
Qwen Code CI / Lint (push) Waiting to run
Qwen Code CI / Test (push) Blocked by required conditions
Qwen Code CI / Test-1 (push) Blocked by required conditions
Qwen Code CI / Test-2 (push) Blocked by required conditions
Qwen Code CI / Test-3 (push) Blocked by required conditions
Qwen Code CI / Test-4 (push) Blocked by required conditions
Qwen Code CI / Test-5 (push) Blocked by required conditions
Qwen Code CI / Test-6 (push) Blocked by required conditions
Qwen Code CI / Test-7 (push) Blocked by required conditions
Qwen Code CI / Test-8 (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
Some checks are pending
Qwen Code CI / Lint (push) Waiting to run
Qwen Code CI / Test (push) Blocked by required conditions
Qwen Code CI / Test-1 (push) Blocked by required conditions
Qwen Code CI / Test-2 (push) Blocked by required conditions
Qwen Code CI / Test-3 (push) Blocked by required conditions
Qwen Code CI / Test-4 (push) Blocked by required conditions
Qwen Code CI / Test-5 (push) Blocked by required conditions
Qwen Code CI / Test-6 (push) Blocked by required conditions
Qwen Code CI / Test-7 (push) Blocked by required conditions
Qwen Code CI / Test-8 (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* refactor(BaseTextInput): ignore literal Tab input in keyboard handler - Prevent insertion of literal tab characters in the BaseTextInput component - Require consumers to intercept Tab via onKeypress for custom behavior - Ensure smoother handling of Tab key without affecting buffer content * fix(cli): preserve tabbed paste in paste workaround - Mark single-line raw chunks containing tabs as paste events in the pasteWorkaround path - Keep a literal Tab key as a non-paste keypress - Add KeypressContext coverage for literal Tab keys and single-line tab-separated raw chunks
This commit is contained in:
parent
ae424e004b
commit
679446d1da
4 changed files with 79 additions and 6 deletions
|
|
@ -211,6 +211,12 @@ export const BaseTextInput: React.FC<BaseTextInputProps> = ({
|
|||
return;
|
||||
}
|
||||
|
||||
// Tab — never insert literal tab characters into the buffer;
|
||||
// consumers that need Tab behaviour should intercept it via onKeypress.
|
||||
if ((key.name === 'tab' || key.sequence === '\t') && !key.paste) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Backspace
|
||||
if (
|
||||
key.name === 'backspace' ||
|
||||
|
|
|
|||
|
|
@ -2320,7 +2320,13 @@ export function useTextBuffer({
|
|||
else if (key.name === 'delete' || (key.ctrl && key.name === 'd')) del();
|
||||
else if (key.ctrl && !key.shift && key.name === 'z') undo();
|
||||
else if (key.ctrl && key.shift && key.name === 'z') redo();
|
||||
else if (input && !key.ctrl && !key.meta) {
|
||||
else if (
|
||||
input &&
|
||||
!key.ctrl &&
|
||||
!key.meta &&
|
||||
key.name !== 'tab' &&
|
||||
input !== '\t'
|
||||
) {
|
||||
insert(input, { paste: key.paste });
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -959,6 +959,63 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('should keep a literal tab key as a non-paste keypress', () => {
|
||||
vi.useFakeTimers();
|
||||
const keyHandler = vi.fn();
|
||||
|
||||
const { result } = renderHook(() => useKeypressContext(), {
|
||||
wrapper: ({ children }) => wrapper({ children, pasteWorkaround: true }),
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.subscribe(keyHandler);
|
||||
});
|
||||
|
||||
try {
|
||||
act(() => {
|
||||
stdin.emit('data', Buffer.from('\t'));
|
||||
});
|
||||
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'tab',
|
||||
sequence: '\t',
|
||||
paste: false,
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it('should mark single-line tabbed raw chunks as paste', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
|
||||
const { result } = renderHook(() => useKeypressContext(), {
|
||||
wrapper: ({ children }) => wrapper({ children, pasteWorkaround: true }),
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.subscribe(keyHandler);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
stdin.emit('data', Buffer.from('first\tsecond'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(keyHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: '',
|
||||
sequence: 'first\tsecond',
|
||||
paste: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should concatenate new data and reset timeout', () => {
|
||||
vi.useFakeTimers();
|
||||
const keyHandler = vi.fn();
|
||||
|
|
|
|||
|
|
@ -989,6 +989,14 @@ export function KeypressProvider({
|
|||
sequence,
|
||||
});
|
||||
|
||||
const shouldFlushRawDataAsPaste = (data: Buffer) => {
|
||||
const hasReturn = data.includes(0x0d);
|
||||
const hasEmbeddedTab = data.length > 1 && data.includes(0x09);
|
||||
const isSingleReturn = data.length <= 2 && hasReturn;
|
||||
|
||||
return !isSingleReturn && (hasReturn || hasEmbeddedTab);
|
||||
};
|
||||
|
||||
const flushRawBuffer = () => {
|
||||
if (!rawDataBuffer.length) {
|
||||
return;
|
||||
|
|
@ -1045,11 +1053,7 @@ export function KeypressProvider({
|
|||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(rawDataBuffer.length <= 2 && rawDataBuffer.includes(0x0d)) ||
|
||||
!rawDataBuffer.includes(0x0d) ||
|
||||
isPaste
|
||||
) {
|
||||
if (isPaste || !shouldFlushRawDataAsPaste(rawDataBuffer)) {
|
||||
keypressStream.write(rawDataBuffer);
|
||||
} else {
|
||||
// Flush raw data buffer as a paste event
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue