mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-27 11:47:46 -07:00
fix: expand paste placeholders in TextInput on submit (#19946)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
@@ -17,7 +17,8 @@ vi.mock('../../hooks/useKeypress.js', () => ({
|
||||
useKeypress: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./text-buffer.js', () => {
|
||||
vi.mock('./text-buffer.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('./text-buffer.js')>();
|
||||
const mockTextBuffer = {
|
||||
text: '',
|
||||
lines: [''],
|
||||
@@ -60,6 +61,7 @@ vi.mock('./text-buffer.js', () => {
|
||||
};
|
||||
|
||||
return {
|
||||
...actual,
|
||||
useTextBuffer: vi.fn(() => mockTextBuffer as unknown as TextBuffer),
|
||||
TextBuffer: vi.fn(() => mockTextBuffer as unknown as TextBuffer),
|
||||
};
|
||||
@@ -82,6 +84,7 @@ describe('TextInput', () => {
|
||||
cursor: [0, 0],
|
||||
visualCursor: [0, 0],
|
||||
viewportVisualLines: [''],
|
||||
pastedContent: {} as Record<string, string>,
|
||||
handleInput: vi.fn((key) => {
|
||||
if (key.sequence) {
|
||||
buffer.text += key.sequence;
|
||||
@@ -298,6 +301,58 @@ describe('TextInput', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('expands paste placeholder to real content on submit', async () => {
|
||||
const placeholder = '[Pasted Text: 6 lines]';
|
||||
const realContent = 'line1\nline2\nline3\nline4\nline5\nline6';
|
||||
mockBuffer.setText(placeholder);
|
||||
mockBuffer.pastedContent = { [placeholder]: realContent };
|
||||
const { waitUntilReady, unmount } = render(
|
||||
<TextInput buffer={mockBuffer} onSubmit={onSubmit} onCancel={onCancel} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
|
||||
|
||||
await act(async () => {
|
||||
keypressHandler({
|
||||
name: 'return',
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
sequence: '',
|
||||
});
|
||||
});
|
||||
await waitUntilReady();
|
||||
|
||||
expect(onSubmit).toHaveBeenCalledWith(realContent);
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('submits text unchanged when pastedContent is empty', async () => {
|
||||
mockBuffer.setText('normal text');
|
||||
mockBuffer.pastedContent = {};
|
||||
const { waitUntilReady, unmount } = render(
|
||||
<TextInput buffer={mockBuffer} onSubmit={onSubmit} onCancel={onCancel} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
|
||||
|
||||
await act(async () => {
|
||||
keypressHandler({
|
||||
name: 'return',
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
sequence: '',
|
||||
});
|
||||
});
|
||||
await waitUntilReady();
|
||||
|
||||
expect(onSubmit).toHaveBeenCalledWith('normal text');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('calls onCancel on escape', async () => {
|
||||
vi.useFakeTimers();
|
||||
const { waitUntilReady, unmount } = render(
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useKeypress } from '../../hooks/useKeypress.js';
|
||||
import chalk from 'chalk';
|
||||
import { theme } from '../../semantic-colors.js';
|
||||
import type { TextBuffer } from './text-buffer.js';
|
||||
import { expandPastePlaceholders } from './text-buffer.js';
|
||||
import { cpSlice, cpIndexToOffset } from '../../utils/textUtils.js';
|
||||
import { keyMatchers, Command } from '../../keyMatchers.js';
|
||||
|
||||
@@ -47,14 +48,14 @@ export function TextInput({
|
||||
}
|
||||
|
||||
if (keyMatchers[Command.SUBMIT](key) && onSubmit) {
|
||||
onSubmit(text);
|
||||
onSubmit(expandPastePlaceholders(text, buffer.pastedContent));
|
||||
return true;
|
||||
}
|
||||
|
||||
const handled = handleInput(key);
|
||||
return handled;
|
||||
},
|
||||
[handleInput, onCancel, onSubmit, text],
|
||||
[handleInput, onCancel, onSubmit, text, buffer.pastedContent],
|
||||
);
|
||||
|
||||
useKeypress(handleKeyPress, { isActive: focus, priority: true });
|
||||
|
||||
@@ -38,6 +38,17 @@ export const LARGE_PASTE_CHAR_THRESHOLD = 500;
|
||||
export const PASTED_TEXT_PLACEHOLDER_REGEX =
|
||||
/\[Pasted Text: \d+ (?:lines|chars)(?: #\d+)?\]/g;
|
||||
|
||||
// Replace paste placeholder strings with their actual pasted content.
|
||||
export function expandPastePlaceholders(
|
||||
text: string,
|
||||
pastedContent: Record<string, string>,
|
||||
): string {
|
||||
return text.replace(
|
||||
PASTED_TEXT_PLACEHOLDER_REGEX,
|
||||
(match) => pastedContent[match] || match,
|
||||
);
|
||||
}
|
||||
|
||||
export type Direction =
|
||||
| 'left'
|
||||
| 'right'
|
||||
@@ -3086,10 +3097,7 @@ export function useTextBuffer({
|
||||
const tmpDir = fs.mkdtempSync(pathMod.join(os.tmpdir(), 'gemini-edit-'));
|
||||
const filePath = pathMod.join(tmpDir, 'buffer.txt');
|
||||
// Expand paste placeholders so user sees full content in editor
|
||||
const expandedText = text.replace(
|
||||
PASTED_TEXT_PLACEHOLDER_REGEX,
|
||||
(match) => pastedContent[match] || match,
|
||||
);
|
||||
const expandedText = expandPastePlaceholders(text, pastedContent);
|
||||
fs.writeFileSync(filePath, expandedText, 'utf8');
|
||||
|
||||
dispatch({ type: 'create_undo_snapshot' });
|
||||
|
||||
Reference in New Issue
Block a user