mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-30 04:30:48 +00:00
Merge branch 'main' into feat/image-attachment
This commit is contained in:
commit
56030f9291
609 changed files with 26677 additions and 12343 deletions
|
|
@ -1190,20 +1190,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||
});
|
||||
|
||||
describe('debug keystroke logging', () => {
|
||||
let consoleLogSpy: ReturnType<typeof vi.spyOn>;
|
||||
let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleLogSpy.mockRestore();
|
||||
consoleWarnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should not log keystrokes when debugKeystrokeLogging is false', async () => {
|
||||
it('should handle kitty sequences when debugKeystrokeLogging is false', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
|
|
@ -1221,138 +1208,20 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||
result.current.subscribe(keyHandler);
|
||||
});
|
||||
|
||||
// Send a kitty sequence
|
||||
// Send a kitty sequence - should work without debug logging
|
||||
act(() => {
|
||||
stdin.sendKittySequence('\x1b[27u');
|
||||
});
|
||||
|
||||
expect(keyHandler).toHaveBeenCalled();
|
||||
expect(consoleLogSpy).not.toHaveBeenCalledWith(
|
||||
expect.stringContaining('[DEBUG] Kitty'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log kitty buffer accumulation when debugKeystrokeLogging is true', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<KeypressProvider
|
||||
kittyProtocolEnabled={true}
|
||||
debugKeystrokeLogging={true}
|
||||
>
|
||||
{children}
|
||||
</KeypressProvider>
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.subscribe(keyHandler);
|
||||
});
|
||||
|
||||
// Send a complete kitty sequence for escape
|
||||
act(() => {
|
||||
stdin.sendKittySequence('\x1b[27u');
|
||||
});
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
'[DEBUG] Kitty buffer accumulating:',
|
||||
expect.stringContaining('\x1b[27u'),
|
||||
);
|
||||
const parsedCall = consoleLogSpy.mock.calls.find(
|
||||
(args) =>
|
||||
typeof args[0] === 'string' &&
|
||||
args[0].includes('[DEBUG] Kitty sequence parsed successfully'),
|
||||
);
|
||||
expect(parsedCall).toBeTruthy();
|
||||
expect(parsedCall?.[1]).toEqual(expect.stringContaining('\x1b[27u'));
|
||||
});
|
||||
|
||||
it('should log kitty buffer overflow when debugKeystrokeLogging is true', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<KeypressProvider
|
||||
kittyProtocolEnabled={true}
|
||||
debugKeystrokeLogging={true}
|
||||
>
|
||||
{children}
|
||||
</KeypressProvider>
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.subscribe(keyHandler);
|
||||
});
|
||||
|
||||
// Send an invalid long sequence to trigger overflow
|
||||
const longInvalidSequence = '\x1b[' + 'x'.repeat(100);
|
||||
act(() => {
|
||||
stdin.sendKittySequence(longInvalidSequence);
|
||||
});
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
'[DEBUG] Kitty buffer overflow, clearing:',
|
||||
expect.any(String),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log kitty buffer clear on Ctrl+C when debugKeystrokeLogging is true', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<KeypressProvider
|
||||
kittyProtocolEnabled={true}
|
||||
debugKeystrokeLogging={true}
|
||||
>
|
||||
{children}
|
||||
</KeypressProvider>
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.subscribe(keyHandler);
|
||||
});
|
||||
|
||||
// Send incomplete kitty sequence
|
||||
act(() => {
|
||||
stdin.pressKey({
|
||||
name: undefined,
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
sequence: '\x1b[1',
|
||||
});
|
||||
});
|
||||
|
||||
// Send Ctrl+C
|
||||
act(() => {
|
||||
stdin.pressKey({
|
||||
name: 'c',
|
||||
ctrl: true,
|
||||
meta: false,
|
||||
shift: false,
|
||||
sequence: '\x03',
|
||||
});
|
||||
});
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
'[DEBUG] Kitty buffer cleared on Ctrl+C:',
|
||||
'\x1b[1',
|
||||
);
|
||||
|
||||
// Verify Ctrl+C was handled
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'c',
|
||||
ctrl: true,
|
||||
name: 'escape',
|
||||
kittyProtocol: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should show char codes when debugKeystrokeLogging is true even without debug mode', async () => {
|
||||
it('should handle kitty sequences when debugKeystrokeLogging is true', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
|
|
@ -1370,29 +1239,44 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||
result.current.subscribe(keyHandler);
|
||||
});
|
||||
|
||||
// Send incomplete kitty sequence
|
||||
const sequence = '\x1b[12';
|
||||
// Send a complete kitty sequence for escape - should work with debug logging
|
||||
act(() => {
|
||||
stdin.pressKey({
|
||||
name: undefined,
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
sequence,
|
||||
});
|
||||
stdin.sendKittySequence('\x1b[27u');
|
||||
});
|
||||
|
||||
// Verify debug logging for accumulation
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
'[DEBUG] Kitty buffer accumulating:',
|
||||
sequence,
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'escape',
|
||||
kittyProtocol: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle kitty buffer overflow without crashing', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<KeypressProvider
|
||||
kittyProtocolEnabled={true}
|
||||
debugKeystrokeLogging={true}
|
||||
>
|
||||
{children}
|
||||
</KeypressProvider>
|
||||
);
|
||||
|
||||
// Verify warning for char codes
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
'Kitty sequence buffer has char codes:',
|
||||
[27, 91, 49, 50],
|
||||
);
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.subscribe(keyHandler);
|
||||
});
|
||||
|
||||
// Send an invalid long sequence to trigger overflow - should not crash
|
||||
const longInvalidSequence = '\x1b[' + 'x'.repeat(100);
|
||||
expect(() => {
|
||||
act(() => {
|
||||
stdin.sendKittySequence(longInvalidSequence);
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import type { Config } from '@qwen-code/qwen-code-core';
|
|||
import {
|
||||
KittySequenceOverflowEvent,
|
||||
logKittySequenceOverflow,
|
||||
createDebugLogger,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { useStdin } from 'ink';
|
||||
import type React from 'react';
|
||||
|
|
@ -62,11 +63,13 @@ export type KeypressHandler = (key: Key) => void;
|
|||
interface KeypressContextValue {
|
||||
subscribe: (handler: KeypressHandler) => void;
|
||||
unsubscribe: (handler: KeypressHandler) => void;
|
||||
pasteWorkaround: boolean;
|
||||
}
|
||||
|
||||
const KeypressContext = createContext<KeypressContextValue | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const debugLogger = createDebugLogger('KEYPRESS');
|
||||
|
||||
export function useKeypressContext() {
|
||||
const context = useContext(KeypressContext);
|
||||
|
|
@ -502,7 +505,7 @@ export function KeypressProvider({
|
|||
key.sequence === `${ESC}${KITTY_CTRL_C}`
|
||||
) {
|
||||
if (kittySequenceBuffer && debugKeystrokeLogging) {
|
||||
console.log(
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty buffer cleared on Ctrl+C:',
|
||||
kittySequenceBuffer,
|
||||
);
|
||||
|
|
@ -536,7 +539,7 @@ export function KeypressProvider({
|
|||
kittySequenceBuffer += key.sequence;
|
||||
|
||||
if (debugKeystrokeLogging) {
|
||||
console.log(
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty buffer accumulating:',
|
||||
kittySequenceBuffer,
|
||||
);
|
||||
|
|
@ -554,7 +557,7 @@ export function KeypressProvider({
|
|||
const nextStart = kittySequenceBuffer.indexOf(`${ESC}[`, 1);
|
||||
if (nextStart > 0) {
|
||||
if (debugKeystrokeLogging) {
|
||||
console.log(
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Skipping incomplete/invalid CSI prefix:',
|
||||
kittySequenceBuffer.slice(0, nextStart),
|
||||
);
|
||||
|
|
@ -570,12 +573,12 @@ export function KeypressProvider({
|
|||
parsed.length,
|
||||
);
|
||||
if (kittySequenceBuffer.length > parsed.length) {
|
||||
console.log(
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty sequence parsed successfully (prefix):',
|
||||
parsedSequence,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty sequence parsed successfully:',
|
||||
parsedSequence,
|
||||
);
|
||||
|
|
@ -592,12 +595,12 @@ export function KeypressProvider({
|
|||
const codes = Array.from(kittySequenceBuffer).map((ch) =>
|
||||
ch.charCodeAt(0),
|
||||
);
|
||||
console.warn('Kitty sequence buffer has char codes:', codes);
|
||||
debugLogger.warn('Kitty sequence buffer has char codes:', codes);
|
||||
}
|
||||
|
||||
if (kittySequenceBuffer.length > MAX_KITTY_SEQUENCE_LENGTH) {
|
||||
if (debugKeystrokeLogging) {
|
||||
console.log(
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty buffer overflow, clearing:',
|
||||
kittySequenceBuffer,
|
||||
);
|
||||
|
|
@ -816,7 +819,9 @@ export function KeypressProvider({
|
|||
]);
|
||||
|
||||
return (
|
||||
<KeypressContext.Provider value={{ subscribe, unsubscribe }}>
|
||||
<KeypressContext.Provider
|
||||
value={{ subscribe, unsubscribe, pasteWorkaround }}
|
||||
>
|
||||
{children}
|
||||
</KeypressContext.Provider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ export interface UIActions {
|
|||
handleFolderTrustSelect: (choice: FolderTrustChoice) => void;
|
||||
setConstrainHeight: (value: boolean) => void;
|
||||
onEscapePromptChange: (show: boolean) => void;
|
||||
onSuggestionsVisibilityChange: (visible: boolean) => void;
|
||||
refreshStatic: () => void;
|
||||
handleFinalSubmit: (value: string) => void;
|
||||
handleClearScreen: () => void;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { createContext, useContext } from 'react';
|
|||
import type {
|
||||
HistoryItem,
|
||||
ThoughtSummary,
|
||||
ConsoleMessageItem,
|
||||
ShellConfirmationRequest,
|
||||
ConfirmationRequest,
|
||||
LoopDetectionConfirmationRequest,
|
||||
|
|
@ -81,8 +80,6 @@ export interface UIState {
|
|||
isFolderTrustDialogOpen: boolean;
|
||||
isTrustedFolder: boolean | undefined;
|
||||
constrainHeight: boolean;
|
||||
showErrorDetails: boolean;
|
||||
filteredConsoleMessages: ConsoleMessageItem[];
|
||||
ideContextState: IdeContext | undefined;
|
||||
showToolDescriptions: boolean;
|
||||
ctrlCPressedOnce: boolean;
|
||||
|
|
@ -96,7 +93,6 @@ export interface UIState {
|
|||
// Quota-related state
|
||||
currentModel: string;
|
||||
contextFileNames: string[];
|
||||
errorCount: number;
|
||||
availableTerminalHeight: number | undefined;
|
||||
mainAreaWidth: number;
|
||||
staticAreaMaxItemHeight: number;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue