From c9e194ec6ae514cd35f1bdd8cebd6cc556a74208 Mon Sep 17 00:00:00 2001
From: Jayson Dasher <58889274+jaysondasher@users.noreply.github.com>
Date: Sat, 12 Jul 2025 00:06:49 -0400
Subject: feat: Add clipboard image paste support for macOS (#1580)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
---
 packages/cli/src/ui/components/InputPrompt.tsx | 63 +++++++++++++++++++++++++-
 1 file changed, 62 insertions(+), 1 deletion(-)

(limited to 'packages/cli/src/ui/components/InputPrompt.tsx')

diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx
index 3771f5b9..371fb48d 100644
--- a/packages/cli/src/ui/components/InputPrompt.tsx
+++ b/packages/cli/src/ui/components/InputPrompt.tsx
@@ -19,6 +19,12 @@ import { useKeypress, Key } from '../hooks/useKeypress.js';
 import { isAtCommand, isSlashCommand } from '../utils/commandUtils.js';
 import { CommandContext, SlashCommand } from '../commands/types.js';
 import { Config } from '@google/gemini-cli-core';
+import {
+  clipboardHasImage,
+  saveClipboardImage,
+  cleanupOldClipboardImages,
+} from '../utils/clipboardUtils.js';
+import * as path from 'path';
 
 export interface InputPromptProps {
   buffer: TextBuffer;
@@ -52,7 +58,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
   setShellModeActive,
 }) => {
   const [justNavigatedHistory, setJustNavigatedHistory] = useState(false);
-
   const completion = useCompletion(
     buffer.text,
     config.getTargetDir(),
@@ -178,6 +183,54 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
     [resetCompletionState, buffer, completionSuggestions, slashCommands],
   );
 
+  // Handle clipboard image pasting with Ctrl+V
+  const handleClipboardImage = useCallback(async () => {
+    try {
+      if (await clipboardHasImage()) {
+        const imagePath = await saveClipboardImage(config.getTargetDir());
+        if (imagePath) {
+          // Clean up old images
+          cleanupOldClipboardImages(config.getTargetDir()).catch(() => {
+            // Ignore cleanup errors
+          });
+
+          // Get relative path from current directory
+          const relativePath = path.relative(config.getTargetDir(), imagePath);
+
+          // Insert @path reference at cursor position
+          const insertText = `@${relativePath}`;
+          const currentText = buffer.text;
+          const [row, col] = buffer.cursor;
+
+          // Calculate offset from row/col
+          let offset = 0;
+          for (let i = 0; i < row; i++) {
+            offset += buffer.lines[i].length + 1; // +1 for newline
+          }
+          offset += col;
+
+          // Add spaces around the path if needed
+          let textToInsert = insertText;
+          const charBefore = offset > 0 ? currentText[offset - 1] : '';
+          const charAfter =
+            offset < currentText.length ? currentText[offset] : '';
+
+          if (charBefore && charBefore !== ' ' && charBefore !== '\n') {
+            textToInsert = ' ' + textToInsert;
+          }
+          if (!charAfter || (charAfter !== ' ' && charAfter !== '\n')) {
+            textToInsert = textToInsert + ' ';
+          }
+
+          // Insert at cursor position
+          buffer.replaceRangeByOffset(offset, offset, textToInsert);
+        }
+      }
+    } catch (error) {
+      console.error('Error handling clipboard image:', error);
+    }
+  }, [buffer, config]);
+
   const handleInput = useCallback(
     (key: Key) => {
       if (!focus) {
@@ -315,6 +368,12 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
         return;
       }
 
+      // Ctrl+V for clipboard image paste
+      if (key.ctrl && key.name === 'v') {
+        handleClipboardImage();
+        return;
+      }
+
       // Fallback to the text buffer's default input handling for all other keys
       buffer.handleInput(key);
     },
@@ -329,6 +388,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
       handleAutocomplete,
       handleSubmitAndClear,
       shellHistory,
+      handleClipboardImage,
     ],
   );
 
@@ -372,6 +432,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
 
               if (visualIdxInRenderedSet === cursorVisualRow) {
                 const relativeVisualColForHighlight = cursorVisualColAbsolute;
+
                 if (relativeVisualColForHighlight >= 0) {
                   if (relativeVisualColForHighlight < cpLen(display)) {
                     const charToHighlight =
-- 
cgit v1.2.3

