mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
Fix(command): line/block Comments Incorrectly Parsed as Slash Commands (#6957)
Co-authored-by: Abhi <43648792+abhipatel12@users.noreply.github.com>
This commit is contained in:
@@ -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;
|
||||
@@ -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) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import type React from 'react';
|
||||
import { Text, Box } from 'ink';
|
||||
import { Colors } from '../../colors.js';
|
||||
import { SCREEN_READER_USER_PREFIX } from '../../constants.js';
|
||||
import { isSlashCommand as checkIsSlashCommand } from '../../utils/commandUtils.js';
|
||||
|
||||
interface UserMessageProps {
|
||||
text: string;
|
||||
@@ -16,7 +17,7 @@ interface UserMessageProps {
|
||||
export const UserMessage: React.FC<UserMessageProps> = ({ text }) => {
|
||||
const prefix = '> ';
|
||||
const prefixWidth = prefix.length;
|
||||
const isSlashCommand = text.startsWith('/');
|
||||
const isSlashCommand = checkIsSlashCommand(text);
|
||||
|
||||
const textColor = isSlashCommand ? Colors.AccentPurple : Colors.Gray;
|
||||
const borderColor = isSlashCommand ? Colors.AccentPurple : Colors.Gray;
|
||||
|
||||
@@ -516,4 +516,81 @@ describe('useCommandCompletion', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('prompt completion filtering', () => {
|
||||
it('should not trigger prompt completion for line comments', async () => {
|
||||
const mockConfig = {
|
||||
getEnablePromptCompletion: () => true,
|
||||
} as Config;
|
||||
|
||||
const { result } = renderHook(() => {
|
||||
const textBuffer = useTextBufferForTest('// This is a line 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 block 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',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -134,7 +134,7 @@ export function useCommandCompletion(
|
||||
if (
|
||||
isPromptCompletionEnabled &&
|
||||
trimmedText.length >= PROMPT_COMPLETION_MIN_LENGTH &&
|
||||
!trimmedText.startsWith('/') &&
|
||||
!isSlashCommand(trimmedText) &&
|
||||
!trimmedText.includes('@')
|
||||
) {
|
||||
return {
|
||||
|
||||
@@ -1089,6 +1089,42 @@ describe('useGeminiStream', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not call handleSlashCommand for line comments', async () => {
|
||||
const { result, mockSendMessageStream: localMockSendMessageStream } =
|
||||
renderTestHook();
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitQuery('// This is a line comment');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
|
||||
expect(localMockSendMessageStream).toHaveBeenCalledWith(
|
||||
'// This is a line comment',
|
||||
expect.any(AbortSignal),
|
||||
expect.any(String),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not call handleSlashCommand for block 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', () => {
|
||||
|
||||
@@ -40,7 +40,7 @@ import type {
|
||||
SlashCommandProcessorResult,
|
||||
} from '../types.js';
|
||||
import { StreamingState, MessageType, 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';
|
||||
@@ -271,7 +271,9 @@ export const useGeminiStream = (
|
||||
await logger?.logMessage(MessageSenderType.USER, trimmedQuery);
|
||||
|
||||
// Handle UI-only commands first
|
||||
const slashCommandResult = await handleSlashCommand(trimmedQuery);
|
||||
const slashCommandResult = isSlashCommand(trimmedQuery)
|
||||
? await handleSlashCommand(trimmedQuery)
|
||||
: false;
|
||||
|
||||
if (slashCommandResult) {
|
||||
switch (slashCommandResult.type) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { Content, GenerateContentConfig } from '@google/genai';
|
||||
import type { 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;
|
||||
@@ -81,7 +82,7 @@ export function usePromptCompletion({
|
||||
if (
|
||||
trimmedText.length < PROMPT_COMPLETION_MIN_LENGTH ||
|
||||
!geminiClient ||
|
||||
trimmedText.startsWith('/') ||
|
||||
isSlashCommand(trimmedText) ||
|
||||
trimmedText.includes('@') ||
|
||||
!isPromptCompletionEnabled
|
||||
) {
|
||||
@@ -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]);
|
||||
|
||||
@@ -102,6 +102,20 @@ describe('commandUtils', () => {
|
||||
expect(isSlashCommand('path/to/file')).toBe(false);
|
||||
expect(isSlashCommand(' /help')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for line comments starting with //', () => {
|
||||
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 block 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', () => {
|
||||
|
||||
@@ -21,12 +21,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 line comments that start with '//'
|
||||
if (query.startsWith('//')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Exclude block comments that start with '/*'
|
||||
if (query.startsWith('/*')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Copies a string snippet to the clipboard for different platforms
|
||||
export const copyToClipboard = async (text: string): Promise<void> => {
|
||||
|
||||
Reference in New Issue
Block a user