From 1d7eb0d25078f34b37a0cbd8a6a869d3e61a2602 Mon Sep 17 00:00:00 2001
From: Sandy Tao <sandytao520@icloud.com>
Date: Thu, 24 Jul 2025 21:41:35 -0700
Subject: [Refactor] Centralizes autocompletion logic within useCompletion
 (#4740)

---
 packages/cli/src/ui/components/InputPrompt.tsx | 124 +------------------------
 1 file changed, 3 insertions(+), 121 deletions(-)

(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 6192fb8c..9f15b56d 100644
--- a/packages/cli/src/ui/components/InputPrompt.tsx
+++ b/packages/cli/src/ui/components/InputPrompt.tsx
@@ -10,13 +10,12 @@ import { Colors } from '../colors.js';
 import { SuggestionsDisplay } from './SuggestionsDisplay.js';
 import { useInputHistory } from '../hooks/useInputHistory.js';
 import { TextBuffer } from './shared/text-buffer.js';
-import { cpSlice, cpLen, toCodePoints } from '../utils/textUtils.js';
+import { cpSlice, cpLen } from '../utils/textUtils.js';
 import chalk from 'chalk';
 import stringWidth from 'string-width';
 import { useShellHistory } from '../hooks/useShellHistory.js';
 import { useCompletion } from '../hooks/useCompletion.js';
 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 {
@@ -59,53 +58,9 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
 }) => {
   const [justNavigatedHistory, setJustNavigatedHistory] = useState(false);
 
-  // Check if cursor is after @ or / without unescaped spaces
-  const isCursorAfterCommandWithoutSpace = useCallback(() => {
-    const [row, col] = buffer.cursor;
-    const currentLine = buffer.lines[row] || '';
-
-    // Convert current line to code points for Unicode-aware processing
-    const codePoints = toCodePoints(currentLine);
-
-    // Search backwards from cursor position within the current line only
-    for (let i = col - 1; i >= 0; i--) {
-      const char = codePoints[i];
-
-      if (char === ' ') {
-        // Check if this space is escaped by counting backslashes before it
-        let backslashCount = 0;
-        for (let j = i - 1; j >= 0 && codePoints[j] === '\\'; j--) {
-          backslashCount++;
-        }
-
-        // If there's an odd number of backslashes, the space is escaped
-        const isEscaped = backslashCount % 2 === 1;
-
-        if (!isEscaped) {
-          // Found unescaped space before @ or /, return false
-          return false;
-        }
-        // If escaped, continue searching backwards
-      } else if (char === '@' || char === '/') {
-        // Found @ or / without unescaped space in between
-        return true;
-      }
-    }
-
-    return false;
-  }, [buffer.cursor, buffer.lines]);
-
-  const shouldShowCompletion = useCallback(
-    () =>
-      (isAtCommand(buffer.text) || isSlashCommand(buffer.text)) &&
-      isCursorAfterCommandWithoutSpace(),
-    [buffer.text, isCursorAfterCommandWithoutSpace],
-  );
-
   const completion = useCompletion(
-    buffer.text,
+    buffer,
     config.getTargetDir(),
-    shouldShowCompletion(),
     slashCommands,
     commandContext,
     config,
@@ -159,78 +114,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
     setJustNavigatedHistory,
   ]);
 
-  const completionSuggestions = completion.suggestions;
-  const handleAutocomplete = useCallback(
-    (indexToUse: number) => {
-      if (indexToUse < 0 || indexToUse >= completionSuggestions.length) {
-        return;
-      }
-      const query = buffer.text;
-      const suggestion = completionSuggestions[indexToUse].value;
-
-      if (query.trimStart().startsWith('/')) {
-        const hasTrailingSpace = query.endsWith(' ');
-        const parts = query
-          .trimStart()
-          .substring(1)
-          .split(/\s+/)
-          .filter(Boolean);
-
-        let isParentPath = false;
-        // If there's no trailing space, we need to check if the current query
-        // is already a complete path to a parent command.
-        if (!hasTrailingSpace) {
-          let currentLevel: readonly SlashCommand[] | undefined = slashCommands;
-          for (let i = 0; i < parts.length; i++) {
-            const part = parts[i];
-            const found: SlashCommand | undefined = currentLevel?.find(
-              (cmd) => cmd.name === part || cmd.altNames?.includes(part),
-            );
-
-            if (found) {
-              if (i === parts.length - 1 && found.subCommands) {
-                isParentPath = true;
-              }
-              currentLevel = found.subCommands as
-                | readonly SlashCommand[]
-                | undefined;
-            } else {
-              // Path is invalid, so it can't be a parent path.
-              currentLevel = undefined;
-              break;
-            }
-          }
-        }
-
-        // Determine the base path of the command.
-        // - If there's a trailing space, the whole command is the base.
-        // - If it's a known parent path, the whole command is the base.
-        // - Otherwise, the base is everything EXCEPT the last partial part.
-        const basePath =
-          hasTrailingSpace || isParentPath ? parts : parts.slice(0, -1);
-        const newValue = `/${[...basePath, suggestion].join(' ')}`;
-
-        buffer.setText(newValue);
-      } else {
-        const atIndex = query.lastIndexOf('@');
-        if (atIndex === -1) return;
-        const pathPart = query.substring(atIndex + 1);
-        const lastSlashIndexInPath = pathPart.lastIndexOf('/');
-        let autoCompleteStartIndex = atIndex + 1;
-        if (lastSlashIndexInPath !== -1) {
-          autoCompleteStartIndex += lastSlashIndexInPath + 1;
-        }
-        buffer.replaceRangeByOffset(
-          autoCompleteStartIndex,
-          buffer.text.length,
-          suggestion,
-        );
-      }
-      resetCompletionState();
-    },
-    [resetCompletionState, buffer, completionSuggestions, slashCommands],
-  );
-
   // Handle clipboard image pasting with Ctrl+V
   const handleClipboardImage = useCallback(async () => {
     try {
@@ -337,7 +220,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
                 ? 0 // Default to the first if none is active
                 : completion.activeSuggestionIndex;
             if (targetIndex < completion.suggestions.length) {
-              handleAutocomplete(targetIndex);
+              completion.handleAutocomplete(targetIndex);
             }
           }
           return;
@@ -459,7 +342,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
       setShellModeActive,
       onClearScreen,
       inputHistory,
-      handleAutocomplete,
       handleSubmitAndClear,
       shellHistory,
       handleClipboardImage,
-- 
cgit v1.2.3

