summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/hooks/atCommandProcessor.ts
diff options
context:
space:
mode:
authorJacob Richman <[email protected]>2025-05-01 18:02:04 -0700
committerGitHub <[email protected]>2025-05-01 18:02:04 -0700
commit53ac7952c7ac11770037fecccda5f0f2fffa3e0b (patch)
tree92309fd1cca72e8f4b1e645e0810b8fff8affdb6 /packages/cli/src/ui/hooks/atCommandProcessor.ts
parentca53565240174eebbe0c03457a8444cae81e2747 (diff)
Support escaping spaces in file paths. (#241)
Diffstat (limited to 'packages/cli/src/ui/hooks/atCommandProcessor.ts')
-rw-r--r--packages/cli/src/ui/hooks/atCommandProcessor.ts98
1 files changed, 88 insertions, 10 deletions
diff --git a/packages/cli/src/ui/hooks/atCommandProcessor.ts b/packages/cli/src/ui/hooks/atCommandProcessor.ts
index da3d71dd..09adc7c0 100644
--- a/packages/cli/src/ui/hooks/atCommandProcessor.ts
+++ b/packages/cli/src/ui/hooks/atCommandProcessor.ts
@@ -7,7 +7,12 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import { PartListUnion } from '@google/genai';
-import { Config, getErrorMessage, isNodeError } from '@gemini-code/server';
+import {
+ Config,
+ getErrorMessage,
+ isNodeError,
+ unescapePath,
+} from '@gemini-code/server';
import {
HistoryItem,
IndividualToolCallDisplay,
@@ -40,6 +45,54 @@ interface HandleAtCommandResult {
}
/**
+ * Parses a query string to find the first '@<path>' command,
+ * handling \ escaped spaces within the path.
+ */
+function parseAtCommand(
+ query: string,
+): { textBefore: string; atPath: string; textAfter: string } | null {
+ let atIndex = -1;
+ for (let i = 0; i < query.length; i++) {
+ // Find the first '@' that is not preceded by a '\'
+ if (query[i] === '@' && (i === 0 || query[i - 1] !== '\\')) {
+ atIndex = i;
+ break;
+ }
+ }
+
+ if (atIndex === -1) {
+ return null; // No '@' command found
+ }
+
+ const textBefore = query.substring(0, atIndex).trim();
+ let pathEndIndex = atIndex + 1;
+ let inEscape = false;
+
+ while (pathEndIndex < query.length) {
+ const char = query[pathEndIndex];
+
+ if (inEscape) {
+ // Current char is escaped, move past it
+ inEscape = false;
+ } else if (char === '\\') {
+ // Start of an escape sequence
+ inEscape = true;
+ } else if (/\s/.test(char)) {
+ // Unescaped whitespace marks the end of the path
+ break;
+ }
+ pathEndIndex++;
+ }
+
+ const rawAtPath = query.substring(atIndex, pathEndIndex);
+ const textAfter = query.substring(pathEndIndex).trim();
+
+ const atPath = unescapePath(rawAtPath);
+
+ return { textBefore, atPath, textAfter };
+}
+
+/**
* Processes user input potentially containing an '@<path>' command.
* It finds the first '@<path>', checks if the path is a file or directory,
* prepares the appropriate path specification for the read_many_files tool,
@@ -58,25 +111,50 @@ export async function handleAtCommand({
userMessageTimestamp,
}: HandleAtCommandParams): Promise<HandleAtCommandResult> {
const trimmedQuery = query.trim();
+ const parsedCommand = parseAtCommand(trimmedQuery);
- const atCommandRegex = /^(.*?)(@\S+)(.*)$/s;
- const match = trimmedQuery.match(atCommandRegex);
-
- if (!match) {
+ if (!parsedCommand) {
+ // If no '@' was found, treat the whole query as user text and proceed
+ // This allows users to just type text without an @ command
+ addHistoryItem(
+ setHistory,
+ { type: 'user', text: query },
+ userMessageTimestamp,
+ );
+ // Let the main hook decide what to do (likely send to LLM)
+ return { processedQuery: [{ text: query }], shouldProceed: true };
+ // Or, if an @ command is *required* when the function is called:
+ /*
const errorTimestamp = getNextMessageId(userMessageTimestamp);
addHistoryItem(
setHistory,
- { type: 'error', text: 'Error: Could not parse @ command.' },
+ { type: 'error', text: 'Error: Could not find @ command.' },
errorTimestamp,
);
return { processedQuery: null, shouldProceed: false };
+ */
}
- const textBefore = match[1].trim();
- const atPath = match[2];
- const textAfter = match[3].trim();
+ const { textBefore, atPath, textAfter } = parsedCommand;
+
+ // Add the original user query to history *before* processing
+ addHistoryItem(
+ setHistory,
+ { type: 'user', text: query },
+ userMessageTimestamp,
+ );
+
+ const pathPart = atPath.substring(1); // Remove the leading '@'
- const pathPart = atPath.substring(1);
+ if (!pathPart) {
+ const errorTimestamp = getNextMessageId(userMessageTimestamp);
+ addHistoryItem(
+ setHistory,
+ { type: 'error', text: 'Error: No path specified after @.' },
+ errorTimestamp,
+ );
+ return { processedQuery: null, shouldProceed: false };
+ }
addHistoryItem(
setHistory,