mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-23 04:21:31 -07:00
feat(cli): Suppress slash command execution and suggestions in shell … (#11380)
This commit is contained in:
@@ -789,6 +789,7 @@ describe('InputPrompt', () => {
|
||||
mockSlashCommands,
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
@@ -816,6 +817,7 @@ describe('InputPrompt', () => {
|
||||
mockSlashCommands,
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
@@ -843,6 +845,7 @@ describe('InputPrompt', () => {
|
||||
mockSlashCommands,
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
@@ -870,6 +873,7 @@ describe('InputPrompt', () => {
|
||||
mockSlashCommands,
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
@@ -897,6 +901,7 @@ describe('InputPrompt', () => {
|
||||
mockSlashCommands,
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
@@ -925,6 +930,7 @@ describe('InputPrompt', () => {
|
||||
mockSlashCommands,
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
@@ -952,6 +958,7 @@ describe('InputPrompt', () => {
|
||||
mockSlashCommands,
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
@@ -980,6 +987,7 @@ describe('InputPrompt', () => {
|
||||
mockSlashCommands,
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
@@ -1008,6 +1016,7 @@ describe('InputPrompt', () => {
|
||||
mockSlashCommands,
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
@@ -1036,6 +1045,7 @@ describe('InputPrompt', () => {
|
||||
mockSlashCommands,
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
@@ -1064,6 +1074,7 @@ describe('InputPrompt', () => {
|
||||
mockSlashCommands,
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
@@ -1094,6 +1105,7 @@ describe('InputPrompt', () => {
|
||||
mockSlashCommands,
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
@@ -1122,6 +1134,7 @@ describe('InputPrompt', () => {
|
||||
mockSlashCommands,
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
@@ -1152,6 +1165,7 @@ describe('InputPrompt', () => {
|
||||
mockSlashCommands,
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
@@ -1161,7 +1175,6 @@ describe('InputPrompt', () => {
|
||||
|
||||
describe('vim mode', () => {
|
||||
it('should not call buffer.handleInput when vim mode is enabled and vim handles the input', async () => {
|
||||
props.vimModeEnabled = true;
|
||||
props.vimHandleInput = vi.fn().mockReturnValue(true); // Mock that vim handled it.
|
||||
const { stdin, unmount } = renderWithProviders(
|
||||
<InputPrompt {...props} />,
|
||||
@@ -1177,7 +1190,6 @@ describe('InputPrompt', () => {
|
||||
});
|
||||
|
||||
it('should call buffer.handleInput when vim mode is enabled but vim does not handle the input', async () => {
|
||||
props.vimModeEnabled = true;
|
||||
props.vimHandleInput = vi.fn().mockReturnValue(false); // Mock that vim did NOT handle it.
|
||||
const { stdin, unmount } = renderWithProviders(
|
||||
<InputPrompt {...props} />,
|
||||
|
||||
@@ -155,6 +155,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
slashCommands,
|
||||
commandContext,
|
||||
reverseSearchActive,
|
||||
shellModeActive,
|
||||
config,
|
||||
);
|
||||
|
||||
|
||||
@@ -121,6 +121,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
),
|
||||
);
|
||||
@@ -146,6 +147,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
);
|
||||
return { completion, textBuffer };
|
||||
@@ -179,6 +181,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
),
|
||||
);
|
||||
@@ -207,6 +210,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
),
|
||||
);
|
||||
@@ -233,6 +237,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
),
|
||||
);
|
||||
@@ -246,6 +251,56 @@ describe('useCommandCompletion', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
shellModeActive: false,
|
||||
expectedSuggestions: 1,
|
||||
expectedShowSuggestions: true,
|
||||
description:
|
||||
'should show slash command suggestions when shellModeActive is false',
|
||||
},
|
||||
{
|
||||
shellModeActive: true,
|
||||
expectedSuggestions: 0,
|
||||
expectedShowSuggestions: false,
|
||||
description:
|
||||
'should not show slash command suggestions when shellModeActive is true',
|
||||
},
|
||||
])(
|
||||
'$description',
|
||||
async ({
|
||||
shellModeActive,
|
||||
expectedSuggestions,
|
||||
expectedShowSuggestions,
|
||||
}) => {
|
||||
setupMocks({
|
||||
slashSuggestions: [{ label: 'clear', value: 'clear' }],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => {
|
||||
const textBuffer = useTextBufferForTest('/');
|
||||
const completion = useCommandCompletion(
|
||||
textBuffer,
|
||||
testDirs,
|
||||
testRootDir,
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
shellModeActive, // Parameterized shellModeActive
|
||||
mockConfig,
|
||||
);
|
||||
return { ...completion, textBuffer };
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.suggestions.length).toBe(expectedSuggestions);
|
||||
expect(result.current.showSuggestions).toBe(
|
||||
expectedShowSuggestions,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('Navigation', () => {
|
||||
@@ -272,6 +327,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
),
|
||||
);
|
||||
@@ -293,6 +349,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
),
|
||||
);
|
||||
@@ -313,6 +370,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
),
|
||||
);
|
||||
@@ -339,6 +397,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
),
|
||||
);
|
||||
@@ -368,6 +427,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
),
|
||||
);
|
||||
@@ -405,6 +465,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
),
|
||||
);
|
||||
@@ -435,6 +496,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
);
|
||||
return { ...completion, textBuffer };
|
||||
@@ -465,6 +527,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
);
|
||||
return { ...completion, textBuffer };
|
||||
@@ -498,6 +561,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
);
|
||||
return { ...completion, textBuffer };
|
||||
@@ -530,6 +594,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
);
|
||||
return { ...completion, textBuffer };
|
||||
@@ -562,6 +627,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
);
|
||||
return { ...completion, textBuffer };
|
||||
@@ -594,6 +660,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
);
|
||||
return { ...completion, textBuffer };
|
||||
@@ -619,6 +686,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
);
|
||||
return { ...completion, textBuffer };
|
||||
@@ -644,6 +712,7 @@ describe('useCommandCompletion', () => {
|
||||
[],
|
||||
mockCommandContext,
|
||||
false,
|
||||
false,
|
||||
mockConfig,
|
||||
);
|
||||
return { ...completion, textBuffer };
|
||||
|
||||
@@ -51,6 +51,7 @@ export function useCommandCompletion(
|
||||
slashCommands: readonly SlashCommand[],
|
||||
commandContext: CommandContext,
|
||||
reverseSearchActive: boolean = false,
|
||||
shellModeActive: boolean,
|
||||
config?: Config,
|
||||
): UseCommandCompletionReturn {
|
||||
const {
|
||||
@@ -163,7 +164,7 @@ export function useCommandCompletion(
|
||||
});
|
||||
|
||||
const slashCompletionRange = useSlashCompletion({
|
||||
enabled: completionMode === CompletionMode.SLASH,
|
||||
enabled: completionMode === CompletionMode.SLASH && !shellModeActive,
|
||||
query,
|
||||
slashCommands,
|
||||
commandContext,
|
||||
|
||||
@@ -1207,6 +1207,39 @@ describe('useGeminiStream', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not call handleSlashCommand is shell mode is active', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useGeminiStream(
|
||||
new MockedGeminiClientClass(mockConfig),
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockLoadedSettings,
|
||||
() => {},
|
||||
mockHandleSlashCommand,
|
||||
true,
|
||||
() => 'vscode' as EditorType,
|
||||
() => {},
|
||||
() => Promise.resolve(),
|
||||
false,
|
||||
() => {},
|
||||
() => {},
|
||||
() => {},
|
||||
() => {},
|
||||
80,
|
||||
24,
|
||||
),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitQuery('/about');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Memory Refresh on save_memory', () => {
|
||||
|
||||
@@ -327,41 +327,43 @@ export const useGeminiStream = (
|
||||
onDebugMessage(`User query: '${trimmedQuery}'`);
|
||||
await logger?.logMessage(MessageSenderType.USER, trimmedQuery);
|
||||
|
||||
// Handle UI-only commands first
|
||||
const slashCommandResult = isSlashCommand(trimmedQuery)
|
||||
? await handleSlashCommand(trimmedQuery)
|
||||
: false;
|
||||
if (!shellModeActive) {
|
||||
// Handle UI-only commands first
|
||||
const slashCommandResult = isSlashCommand(trimmedQuery)
|
||||
? await handleSlashCommand(trimmedQuery)
|
||||
: false;
|
||||
|
||||
if (slashCommandResult) {
|
||||
switch (slashCommandResult.type) {
|
||||
case 'schedule_tool': {
|
||||
const { toolName, toolArgs } = slashCommandResult;
|
||||
const toolCallRequest: ToolCallRequestInfo = {
|
||||
callId: `${toolName}-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
||||
name: toolName,
|
||||
args: toolArgs,
|
||||
isClientInitiated: true,
|
||||
prompt_id,
|
||||
};
|
||||
scheduleToolCalls([toolCallRequest], abortSignal);
|
||||
return { queryToSend: null, shouldProceed: false };
|
||||
}
|
||||
case 'submit_prompt': {
|
||||
localQueryToSendToGemini = slashCommandResult.content;
|
||||
if (slashCommandResult) {
|
||||
switch (slashCommandResult.type) {
|
||||
case 'schedule_tool': {
|
||||
const { toolName, toolArgs } = slashCommandResult;
|
||||
const toolCallRequest: ToolCallRequestInfo = {
|
||||
callId: `${toolName}-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
||||
name: toolName,
|
||||
args: toolArgs,
|
||||
isClientInitiated: true,
|
||||
prompt_id,
|
||||
};
|
||||
scheduleToolCalls([toolCallRequest], abortSignal);
|
||||
return { queryToSend: null, shouldProceed: false };
|
||||
}
|
||||
case 'submit_prompt': {
|
||||
localQueryToSendToGemini = slashCommandResult.content;
|
||||
|
||||
return {
|
||||
queryToSend: localQueryToSendToGemini,
|
||||
shouldProceed: true,
|
||||
};
|
||||
}
|
||||
case 'handled': {
|
||||
return { queryToSend: null, shouldProceed: false };
|
||||
}
|
||||
default: {
|
||||
const unreachable: never = slashCommandResult;
|
||||
throw new Error(
|
||||
`Unhandled slash command result type: ${unreachable}`,
|
||||
);
|
||||
return {
|
||||
queryToSend: localQueryToSendToGemini,
|
||||
shouldProceed: true,
|
||||
};
|
||||
}
|
||||
case 'handled': {
|
||||
return { queryToSend: null, shouldProceed: false };
|
||||
}
|
||||
default: {
|
||||
const unreachable: never = slashCommandResult;
|
||||
throw new Error(
|
||||
`Unhandled slash command result type: ${unreachable}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user