From 589f5e6823eca456d9c93cadea664e5c19eebb90 Mon Sep 17 00:00:00 2001
From: 官余棚 <1@linux.com>
Date: Thu, 21 Aug 2025 16:04:04 +0800
Subject: feat(cli): prompt completion (#4691)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
---
 packages/cli/src/ui/hooks/useCommandCompletion.tsx | 49 ++++++++++++++++++----
 1 file changed, 41 insertions(+), 8 deletions(-)

(limited to 'packages/cli/src/ui/hooks/useCommandCompletion.tsx')

diff --git a/packages/cli/src/ui/hooks/useCommandCompletion.tsx b/packages/cli/src/ui/hooks/useCommandCompletion.tsx
index 07d0e056..166f03c5 100644
--- a/packages/cli/src/ui/hooks/useCommandCompletion.tsx
+++ b/packages/cli/src/ui/hooks/useCommandCompletion.tsx
@@ -15,6 +15,11 @@ import { isSlashCommand } from '../utils/commandUtils.js';
 import { toCodePoints } from '../utils/textUtils.js';
 import { useAtCompletion } from './useAtCompletion.js';
 import { useSlashCompletion } from './useSlashCompletion.js';
+import {
+  usePromptCompletion,
+  PromptCompletion,
+  PROMPT_COMPLETION_MIN_LENGTH,
+} from './usePromptCompletion.js';
 import { Config } from '@google/gemini-cli-core';
 import { useCompletion } from './useCompletion.js';
 
@@ -22,6 +27,7 @@ export enum CompletionMode {
   IDLE = 'IDLE',
   AT = 'AT',
   SLASH = 'SLASH',
+  PROMPT = 'PROMPT',
 }
 
 export interface UseCommandCompletionReturn {
@@ -37,6 +43,7 @@ export interface UseCommandCompletionReturn {
   navigateUp: () => void;
   navigateDown: () => void;
   handleAutocomplete: (indexToUse: number) => void;
+  promptCompletion: PromptCompletion;
 }
 
 export function useCommandCompletion(
@@ -93,12 +100,7 @@ export function useCommandCompletion(
             backslashCount++;
           }
           if (backslashCount % 2 === 0) {
-            return {
-              completionMode: CompletionMode.IDLE,
-              query: null,
-              completionStart: -1,
-              completionEnd: -1,
-            };
+            break;
           }
         } else if (char === '@') {
           let end = codePoints.length;
@@ -125,13 +127,33 @@ export function useCommandCompletion(
           };
         }
       }
+
+      // Check for prompt completion - only if enabled
+      const trimmedText = buffer.text.trim();
+      const isPromptCompletionEnabled =
+        config?.getEnablePromptCompletion() ?? false;
+
+      if (
+        isPromptCompletionEnabled &&
+        trimmedText.length >= PROMPT_COMPLETION_MIN_LENGTH &&
+        !trimmedText.startsWith('/') &&
+        !trimmedText.includes('@')
+      ) {
+        return {
+          completionMode: CompletionMode.PROMPT,
+          query: trimmedText,
+          completionStart: 0,
+          completionEnd: trimmedText.length,
+        };
+      }
+
       return {
         completionMode: CompletionMode.IDLE,
         query: null,
         completionStart: -1,
         completionEnd: -1,
       };
-    }, [cursorRow, cursorCol, buffer.lines]);
+    }, [cursorRow, cursorCol, buffer.lines, buffer.text, config]);
 
   useAtCompletion({
     enabled: completionMode === CompletionMode.AT,
@@ -152,6 +174,12 @@ export function useCommandCompletion(
     setIsPerfectMatch,
   });
 
+  const promptCompletion = usePromptCompletion({
+    buffer,
+    config,
+    enabled: completionMode === CompletionMode.PROMPT,
+  });
+
   useEffect(() => {
     setActiveSuggestionIndex(suggestions.length > 0 ? 0 : -1);
     setVisibleStartIndex(0);
@@ -202,7 +230,11 @@ export function useCommandCompletion(
         }
       }
 
-      suggestionText += ' ';
+      const lineCodePoints = toCodePoints(buffer.lines[cursorRow] || '');
+      const charAfterCompletion = lineCodePoints[end];
+      if (charAfterCompletion !== ' ') {
+        suggestionText += ' ';
+      }
 
       buffer.replaceRangeByOffset(
         logicalPosToOffset(buffer.lines, cursorRow, start),
@@ -234,5 +266,6 @@ export function useCommandCompletion(
     navigateUp,
     navigateDown,
     handleAutocomplete,
+    promptCompletion,
   };
 }
-- 
cgit v1.2.3

