Skip to content

Commit e543acc

Browse files
committed
Improve clipboard image pasting: sequential numbering and PowerShell support
- Changed image filenames from timestamp-based (clipboard-1234567890.png) to sequential numbering (image-1.png, image-2.png) - Updated display format to show friendly labels: [image #1] @path instead of just @path - Added Shift+Insert as alternative paste key binding for PowerShell compatibility (PowerShell intercepts Ctrl+V) - Updated cleanup function to handle both old and new filename formats This addresses the PowerShell Ctrl+V limitation and improves UX with clearer image references.
1 parent a2390ee commit e543acc

File tree

3 files changed

+50
-18
lines changed

3 files changed

+50
-18
lines changed

packages/cli/src/config/keyBindings.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,10 @@ export const defaultKeyBindings: KeyBindingConfig = {
192192
{ key: 'x', ctrl: true },
193193
{ sequence: '\x18', ctrl: true },
194194
],
195-
[Command.PASTE_CLIPBOARD]: [{ key: 'v', ctrl: true }],
195+
[Command.PASTE_CLIPBOARD]: [
196+
{ key: 'v', ctrl: true },
197+
{ key: 'insert', shift: true },
198+
],
196199

197200
// App level bindings
198201
[Command.SHOW_ERROR_DETAILS]: [{ key: 'f12' }],

packages/cli/src/ui/components/InputPrompt.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,12 +334,17 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
334334
// Get relative path from current directory
335335
const relativePath = path.relative(config.getTargetDir(), imagePath);
336336

337-
// Insert @path reference at cursor position
338-
const insertText = `@${relativePath}`;
337+
// Extract image number from filename (e.g., "image-1.png" -> 1)
338+
const filename = path.basename(imagePath);
339+
const imageNumberMatch = filename.match(/image-(\d+)\./);
340+
const imageNumber = imageNumberMatch ? imageNumberMatch[1] : '?';
341+
342+
// Insert with friendly label: [image #1] @path
343+
const insertText = `[image #${imageNumber}] @${relativePath}`;
339344
const currentText = buffer.text;
340345
const offset = buffer.getOffset();
341346

342-
// Add spaces around the path if needed
347+
// Add spaces around the text if needed
343348
let textToInsert = insertText;
344349
const charBefore = offset > 0 ? currentText[offset - 1] : '';
345350
const charAfter =

packages/cli/src/ui/utils/clipboardUtils.ts

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,35 @@ async function wslClipboardHasImage(): Promise<boolean> {
8686
}
8787
}
8888

89+
/**
90+
* Gets the next available image number by checking existing files
91+
* @param targetDir The target directory
92+
* @returns The next image number
93+
*/
94+
async function getNextImageNumber(targetDir: string): Promise<number> {
95+
try {
96+
const tempDir = path.join(targetDir, '.gemini-clipboard');
97+
await fs.mkdir(tempDir, { recursive: true });
98+
99+
const files = await fs.readdir(tempDir);
100+
const imageFiles = files.filter((f) => f.match(/^image-(\d+)\.png$/));
101+
102+
if (imageFiles.length === 0) {
103+
return 1;
104+
}
105+
106+
// Extract numbers and find the max
107+
const numbers = imageFiles.map((f) => {
108+
const match = f.match(/^image-(\d+)\.png$/);
109+
return match ? parseInt(match[1], 10) : 0;
110+
});
111+
112+
return Math.max(...numbers) + 1;
113+
} catch {
114+
return 1;
115+
}
116+
}
117+
89118
/**
90119
* Saves clipboard image using PowerShell (WSL)
91120
* @param targetDir The target directory to save the image
@@ -106,9 +135,9 @@ async function wslSaveClipboardImage(
106135
const tempDir = path.join(baseDir, '.gemini-clipboard');
107136
await fs.mkdir(tempDir, { recursive: true });
108137

109-
// Generate unique filename
110-
const timestamp = new Date().getTime();
111-
const wslPath = path.join(tempDir, `clipboard-${timestamp}.png`);
138+
// Get next image number
139+
const imageNumber = await getNextImageNumber(baseDir);
140+
const wslPath = path.join(tempDir, `image-${imageNumber}.png`);
112141

113142
// Convert WSL path to Windows path for PowerShell
114143
const { stdout: winPath } = await spawnAsync('wslpath', ['-w', wslPath]);
@@ -269,14 +298,14 @@ export async function saveClipboardImage(
269298
const tempDir = path.join(baseDir, '.gemini-clipboard');
270299
await fs.mkdir(tempDir, { recursive: true });
271300

272-
// Generate a unique filename with timestamp
273-
const timestamp = new Date().getTime();
301+
// Get next image number
302+
const imageNumber = await getNextImageNumber(baseDir);
274303

275304
// Detect image format from magic bytes
276305
const extension = detectImageFormat(base64Data);
277306
const tempFilePath = path.join(
278307
tempDir,
279-
`clipboard-${timestamp}.${extension}`,
308+
`image-${imageNumber}.${extension}`,
280309
);
281310

282311
// Convert base64 to buffer and save to file
@@ -317,14 +346,9 @@ export async function cleanupOldClipboardImages(
317346

318347
for (const file of files) {
319348
if (
320-
file.startsWith('clipboard-') &&
321-
(file.endsWith('.png') ||
322-
file.endsWith('.jpg') ||
323-
file.endsWith('.jpeg') ||
324-
file.endsWith('.tiff') ||
325-
file.endsWith('.gif') ||
326-
file.endsWith('.bmp') ||
327-
file.endsWith('.webp'))
349+
file.match(
350+
/^(clipboard-\d+|image-\d+)\.(png|jpg|jpeg|tiff|gif|bmp|webp)$/,
351+
)
328352
) {
329353
const filePath = path.join(tempDir, file);
330354
const stats = await fs.stat(filePath);

0 commit comments

Comments
 (0)