mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-30 20:50:34 +00:00
fix(paste): move thresholds to module level and improve placeholder expansion
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
21ae35b221
commit
3b4b5d874d
2 changed files with 81 additions and 9 deletions
|
|
@ -2244,6 +2244,67 @@ describe('InputPrompt', () => {
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should expand same-size placeholders correctly when #2 appears first', async () => {
|
||||||
|
const firstPaste = 'x'.repeat(1001);
|
||||||
|
const secondPaste = 'y'.repeat(1001);
|
||||||
|
|
||||||
|
const { stdin, unmount } = renderWithProviders(
|
||||||
|
<InputPrompt {...props} />,
|
||||||
|
);
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
stdin.write(`\x1b[200~${firstPaste}\x1b[201~`);
|
||||||
|
await wait();
|
||||||
|
stdin.write(`\x1b[200~${secondPaste}\x1b[201~`);
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
mockBuffer.text =
|
||||||
|
'[Pasted Content 1001 chars] #2\n[Pasted Content 1001 chars]';
|
||||||
|
mockBuffer.lines = mockBuffer.text.split('\n');
|
||||||
|
mockBuffer.cursor = [1, '[Pasted Content 1001 chars]'.length];
|
||||||
|
|
||||||
|
// Wait for paste protection to expire
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 600));
|
||||||
|
|
||||||
|
stdin.write('\r');
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
expect(props.onSubmit).toHaveBeenCalledWith(
|
||||||
|
`${secondPaste}\n${firstPaste}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should write expanded placeholder content to shell history', async () => {
|
||||||
|
props.shellModeActive = true;
|
||||||
|
const largeContent = 'x'.repeat(1001);
|
||||||
|
mockBuffer.text = '[Pasted Content 1001 chars]';
|
||||||
|
mockBuffer.lines = [mockBuffer.text];
|
||||||
|
mockBuffer.cursor = [0, mockBuffer.text.length];
|
||||||
|
|
||||||
|
const { stdin, unmount } = renderWithProviders(
|
||||||
|
<InputPrompt {...props} />,
|
||||||
|
);
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
stdin.write(`\x1b[200~${largeContent}\x1b[201~`);
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
// Wait for paste protection to expire
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 600));
|
||||||
|
|
||||||
|
stdin.write('\r');
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
expect(mockShellHistory.addCommandToHistory).toHaveBeenCalledWith(
|
||||||
|
largeContent,
|
||||||
|
);
|
||||||
|
expect(props.onSubmit).toHaveBeenCalledWith(largeContent);
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
});
|
||||||
|
|
||||||
it('should delete entire placeholder on backspace', async () => {
|
it('should delete entire placeholder on backspace', async () => {
|
||||||
const placeholderText = '[Pasted Content 1001 chars]';
|
const placeholderText = '[Pasted Content 1001 chars]';
|
||||||
mockBuffer.text = placeholderText;
|
mockBuffer.text = placeholderText;
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,10 @@ export const calculatePromptWidths = (terminalWidth: number) => {
|
||||||
} as const;
|
} as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Large paste placeholder thresholds
|
||||||
|
const LARGE_PASTE_CHAR_THRESHOLD = 1000;
|
||||||
|
const LARGE_PASTE_LINE_THRESHOLD = 10;
|
||||||
|
|
||||||
export const InputPrompt: React.FC<InputPromptProps> = ({
|
export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||||
buffer,
|
buffer,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
|
@ -121,7 +125,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||||
const pasteTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const pasteTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
// Large paste placeholder handling
|
// Large paste placeholder handling
|
||||||
const LARGE_PASTE_CHAR_THRESHOLD = 1000;
|
|
||||||
const [pendingPastes, setPendingPastes] = useState<Map<string, string>>(
|
const [pendingPastes, setPendingPastes] = useState<Map<string, string>>(
|
||||||
new Map(),
|
new Map(),
|
||||||
);
|
);
|
||||||
|
|
@ -255,16 +258,26 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||||
|
|
||||||
const handleSubmitAndClear = useCallback(
|
const handleSubmitAndClear = useCallback(
|
||||||
(submittedValue: string) => {
|
(submittedValue: string) => {
|
||||||
if (shellModeActive) {
|
|
||||||
shellHistory.addCommandToHistory(submittedValue);
|
|
||||||
}
|
|
||||||
// Expand any large paste placeholders to their full content before submitting
|
// Expand any large paste placeholders to their full content before submitting
|
||||||
let finalValue = submittedValue;
|
let finalValue = submittedValue;
|
||||||
if (pendingPastes.size > 0) {
|
if (pendingPastes.size > 0) {
|
||||||
pendingPastes.forEach((fullContent, placeholder) => {
|
const placeholders = Array.from(pendingPastes.keys()).sort(
|
||||||
finalValue = finalValue.replace(placeholder, fullContent);
|
(a, b) => b.length - a.length,
|
||||||
});
|
);
|
||||||
|
const escapedPlaceholders = placeholders.map((placeholderValue) =>
|
||||||
|
placeholderValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
|
||||||
|
);
|
||||||
|
const placeholderRegex = new RegExp(escapedPlaceholders.join('|'), 'g');
|
||||||
|
finalValue = finalValue.replace(
|
||||||
|
placeholderRegex,
|
||||||
|
(matchedPlaceholder) =>
|
||||||
|
pendingPastes.get(matchedPlaceholder) ?? matchedPlaceholder,
|
||||||
|
);
|
||||||
setPendingPastes(new Map());
|
setPendingPastes(new Map());
|
||||||
|
activePlaceholderIds.current.clear();
|
||||||
|
}
|
||||||
|
if (shellModeActive) {
|
||||||
|
shellHistory.addCommandToHistory(finalValue);
|
||||||
}
|
}
|
||||||
// Clear the buffer *before* calling onSubmit to prevent potential re-submission
|
// Clear the buffer *before* calling onSubmit to prevent potential re-submission
|
||||||
// if onSubmit triggers a re-render while the buffer still holds the old value.
|
// if onSubmit triggers a re-render while the buffer still holds the old value.
|
||||||
|
|
@ -397,7 +410,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||||
const pasted = key.sequence.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
const pasted = key.sequence.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
||||||
const charCount = [...pasted].length; // Proper Unicode char count
|
const charCount = [...pasted].length; // Proper Unicode char count
|
||||||
const lineCount = pasted.split('\n').length;
|
const lineCount = pasted.split('\n').length;
|
||||||
const LARGE_PASTE_LINE_THRESHOLD = 10;
|
|
||||||
if (
|
if (
|
||||||
charCount > LARGE_PASTE_CHAR_THRESHOLD ||
|
charCount > LARGE_PASTE_CHAR_THRESHOLD ||
|
||||||
lineCount > LARGE_PASTE_LINE_THRESHOLD
|
lineCount > LARGE_PASTE_LINE_THRESHOLD
|
||||||
|
|
@ -852,7 +864,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||||
uiState,
|
uiState,
|
||||||
uiActions,
|
uiActions,
|
||||||
pasteWorkaround,
|
pasteWorkaround,
|
||||||
LARGE_PASTE_CHAR_THRESHOLD,
|
|
||||||
nextLargePastePlaceholder,
|
nextLargePastePlaceholder,
|
||||||
pendingPastes,
|
pendingPastes,
|
||||||
parsePlaceholder,
|
parsePlaceholder,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue