Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/cli/src/ui/components/SuggestionsDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { Box, Text } from 'ink';
import { Colors } from '../colors.js';
import { PrepareLabel } from './PrepareLabel.js';
import { isSlashCommand } from '../utils/commandUtils.js';
export interface Suggestion {
label: string;
value: string;
Expand Down Expand Up @@ -52,7 +53,7 @@ export function SuggestionsDisplay({
);
const visibleSuggestions = suggestions.slice(startIndex, endIndex);

const isSlashCommandMode = userInput.startsWith('/');
const isSlashCommandMode = isSlashCommand(userInput);
let commandNameWidth = 0;

if (isSlashCommandMode) {
Expand Down
7 changes: 4 additions & 3 deletions packages/cli/src/ui/components/messages/UserMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import React from 'react';
import { Text, Box } from 'ink';
import { Colors } from '../../colors.js';
import { SCREEN_READER_USER_PREFIX } from '../../constants.js';
import { isSlashCommand } from '../../utils/commandUtils.js';

interface UserMessageProps {
text: string;
Expand All @@ -16,10 +17,10 @@ interface UserMessageProps {
export const UserMessage: React.FC<UserMessageProps> = ({ text }) => {
const prefix = '> ';
const prefixWidth = prefix.length;
const isSlashCommand = text.startsWith('/');
const isCommand = isSlashCommand(text);

const textColor = isSlashCommand ? Colors.AccentPurple : Colors.Gray;
const borderColor = isSlashCommand ? Colors.AccentPurple : Colors.Gray;
const textColor = isCommand ? Colors.AccentPurple : Colors.Gray;
Comment thread
lifefloating marked this conversation as resolved.
Outdated
const borderColor = isCommand ? Colors.AccentPurple : Colors.Gray;

return (
<Box
Expand Down
79 changes: 79 additions & 0 deletions packages/cli/src/ui/hooks/useCommandCompletion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,4 +517,83 @@ describe('useCommandCompletion', () => {
);
});
});

describe('prompt completion filtering', () => {
it('should not trigger prompt completion for JavaScript comments', async () => {
Comment thread
lifefloating marked this conversation as resolved.
Outdated
const mockConfig = {
getEnablePromptCompletion: () => true,
} as Config;

const { result } = renderHook(() => {
const textBuffer = useTextBufferForTest(
'// This is a JavaScript comment',
);
const completion = useCommandCompletion(
textBuffer,
testDirs,
testRootDir,
[],
mockCommandContext,
false,
mockConfig,
);
return { ...completion, textBuffer };
});

// Should not trigger prompt completion for comments
expect(result.current.suggestions.length).toBe(0);
});

it('should not trigger prompt completion for C-style comments', async () => {
const mockConfig = {
getEnablePromptCompletion: () => true,
} as Config;

const { result } = renderHook(() => {
const textBuffer = useTextBufferForTest(
'/* This is a block comment */',
);
const completion = useCommandCompletion(
textBuffer,
testDirs,
testRootDir,
[],
mockCommandContext,
false,
mockConfig,
);
return { ...completion, textBuffer };
});

// Should not trigger prompt completion for comments
expect(result.current.suggestions.length).toBe(0);
});

it('should trigger prompt completion for regular text when enabled', async () => {
const mockConfig = {
getEnablePromptCompletion: () => true,
} as Config;

const { result } = renderHook(() => {
const textBuffer = useTextBufferForTest(
'This is regular text that should trigger completion',
);
const completion = useCommandCompletion(
textBuffer,
testDirs,
testRootDir,
[],
mockCommandContext,
false,
mockConfig,
);
return { ...completion, textBuffer };
});

// This test verifies that comments are filtered out while regular text is not
expect(result.current.textBuffer.text).toBe(
'This is regular text that should trigger completion',
);
});
});
});
2 changes: 1 addition & 1 deletion packages/cli/src/ui/hooks/useCommandCompletion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export function useCommandCompletion(
if (
isPromptCompletionEnabled &&
trimmedText.length >= PROMPT_COMPLETION_MIN_LENGTH &&
!trimmedText.startsWith('/') &&
!isSlashCommand(trimmedText) &&
!trimmedText.includes('@')
) {
return {
Expand Down
36 changes: 36 additions & 0 deletions packages/cli/src/ui/hooks/useGeminiStream.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,42 @@ describe('useGeminiStream', () => {
);
});
});

it('should not call handleSlashCommand for JavaScript comments', async () => {
Comment thread
lifefloating marked this conversation as resolved.
Outdated
const { result, mockSendMessageStream: localMockSendMessageStream } =
renderTestHook();

await act(async () => {
await result.current.submitQuery('// This is a JavaScript comment');
});

await waitFor(() => {
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
expect(localMockSendMessageStream).toHaveBeenCalledWith(
'// This is a JavaScript comment',
expect.any(AbortSignal),
expect.any(String),
);
});
});

it('should not call handleSlashCommand for C-style comments', async () => {
const { result, mockSendMessageStream: localMockSendMessageStream } =
renderTestHook();

await act(async () => {
await result.current.submitQuery('/* This is a block comment */');
});

await waitFor(() => {
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
expect(localMockSendMessageStream).toHaveBeenCalledWith(
'/* This is a block comment */',
expect.any(AbortSignal),
expect.any(String),
);
});
});
});

describe('Memory Refresh on save_memory', () => {
Expand Down
8 changes: 5 additions & 3 deletions packages/cli/src/ui/hooks/useGeminiStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {
SlashCommandProcessorResult,
ToolCallStatus,
} from '../types.js';
import { isAtCommand } from '../utils/commandUtils.js';
import { isAtCommand, isSlashCommand } from '../utils/commandUtils.js';
import { useShellCommandProcessor } from './shellCommandProcessor.js';
import { handleAtCommand } from './atCommandProcessor.js';
import { findLastSafeSplitPoint } from '../utils/markdownUtilities.js';
Expand Down Expand Up @@ -244,8 +244,10 @@ export const useGeminiStream = (
onDebugMessage(`User query: '${trimmedQuery}'`);
await logger?.logMessage(MessageSenderType.USER, trimmedQuery);

// Handle UI-only commands first
const slashCommandResult = await handleSlashCommand(trimmedQuery);
// Handle UI-only commands first (only if it's actually a slash command)
Comment thread
lifefloating marked this conversation as resolved.
Outdated
const slashCommandResult = isSlashCommand(trimmedQuery)
? await handleSlashCommand(trimmedQuery)
: false;

if (slashCommandResult) {
switch (slashCommandResult.type) {
Expand Down
5 changes: 3 additions & 2 deletions packages/cli/src/ui/hooks/usePromptCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '@google/gemini-cli-core';
import { Content, GenerateContentConfig } from '@google/genai';
import { TextBuffer } from '../components/shared/text-buffer.js';
import { isSlashCommand } from '../utils/commandUtils.js';

export const PROMPT_COMPLETION_MIN_LENGTH = 5;
export const PROMPT_COMPLETION_DEBOUNCE_MS = 250;
Expand Down Expand Up @@ -81,7 +82,7 @@ export function usePromptCompletion({
if (
trimmedText.length < PROMPT_COMPLETION_MIN_LENGTH ||
!geminiClient ||
trimmedText.startsWith('/') ||
isSlashCommand(trimmedText) ||
trimmedText.includes('@') ||
!isPromptCompletionEnabled
) {
Expand Down Expand Up @@ -237,7 +238,7 @@ export function usePromptCompletion({
const trimmedText = buffer.text.trim();
return (
trimmedText.length >= PROMPT_COMPLETION_MIN_LENGTH &&
!trimmedText.startsWith('/') &&
!isSlashCommand(trimmedText) &&
!trimmedText.includes('@')
);
}, [buffer.text, isPromptCompletionEnabled, isCursorAtEnd]);
Expand Down
14 changes: 14 additions & 0 deletions packages/cli/src/ui/utils/commandUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,20 @@ describe('commandUtils', () => {
expect(isSlashCommand('path/to/file')).toBe(false);
expect(isSlashCommand(' /help')).toBe(false);
});

it('should return false for JavaScript comments starting with //', () => {
Comment thread
lifefloating marked this conversation as resolved.
Outdated
expect(isSlashCommand('// This is a comment')).toBe(false);
expect(isSlashCommand('// check if variants base info all filled.')).toBe(
false,
);
expect(isSlashCommand('//comment without space')).toBe(false);
});

it('should return false for C-style comments starting with /*', () => {
expect(isSlashCommand('/* This is a block comment */')).toBe(false);
expect(isSlashCommand('/*\n * Multi-line comment\n */')).toBe(false);
expect(isSlashCommand('/*comment without space*/')).toBe(false);
});
});

describe('copyToClipboard', () => {
Expand Down
20 changes: 18 additions & 2 deletions packages/cli/src/ui/utils/commandUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,28 @@ export const isAtCommand = (query: string): boolean =>

/**
* Checks if a query string potentially represents an '/' command.
* It triggers if the query starts with '/'
* It triggers if the query starts with '/' but excludes code comments like '//' and '/*'.
*
* @param query The input query string.
* @returns True if the query looks like an '/' command, false otherwise.
*/
export const isSlashCommand = (query: string): boolean => query.startsWith('/');
export const isSlashCommand = (query: string): boolean => {
if (!query.startsWith('/')) {
return false;
}

// Exclude JavaScript/C++ style comments that start with '//'
Comment thread
lifefloating marked this conversation as resolved.
Outdated
if (query.startsWith('//')) {
return false;
}

// Exclude C-style block comments that start with '/*'
if (query.startsWith('/*')) {
return false;
}

return true;
};
Comment thread
abhipatel12 marked this conversation as resolved.

// Copies a string snippet to the clipboard for different platforms
export const copyToClipboard = async (text: string): Promise<void> => {
Expand Down