From 4915050ad47236a6d8349ed87b68cd202f96efbe Mon Sep 17 00:00:00 2001
From: Sandy Tao <sandytao520@icloud.com>
Date: Fri, 18 Jul 2025 14:54:10 -0700
Subject: improve command completion trigger logic based on cursor position
 (#4462)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
---
 packages/cli/src/ui/components/InputPrompt.tsx | 48 ++++++++++++++++++++++++--
 1 file changed, 46 insertions(+), 2 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 4d66b10c..46326431 100644
--- a/packages/cli/src/ui/components/InputPrompt.tsx
+++ b/packages/cli/src/ui/components/InputPrompt.tsx
@@ -10,7 +10,7 @@ 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 } from '../utils/textUtils.js';
+import { cpSlice, cpLen, toCodePoints } from '../utils/textUtils.js';
 import chalk from 'chalk';
 import stringWidth from 'string-width';
 import { useShellHistory } from '../hooks/useShellHistory.js';
@@ -58,10 +58,54 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
   setShellModeActive,
 }) => {
   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,
     config.getTargetDir(),
-    isAtCommand(buffer.text) || isSlashCommand(buffer.text),
+    shouldShowCompletion(),
     slashCommands,
     commandContext,
     config,
-- 
cgit v1.2.3

