mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
Allow for slash commands to opt-out of autocompletion and /help discovery. (#7847)
This commit is contained in:
@@ -349,36 +349,4 @@ describe('CommandService', () => {
|
|||||||
expect(deployExtension).toBeDefined();
|
expect(deployExtension).toBeDefined();
|
||||||
expect(deployExtension?.description).toBe('[gcp] Deploy to Google Cloud');
|
expect(deployExtension?.description).toBe('[gcp] Deploy to Google Cloud');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should filter out hidden commands', async () => {
|
|
||||||
const visibleCommand = createMockCommand('visible', CommandKind.BUILT_IN);
|
|
||||||
const hiddenCommand = {
|
|
||||||
...createMockCommand('hidden', CommandKind.BUILT_IN),
|
|
||||||
hidden: true,
|
|
||||||
};
|
|
||||||
const initiallyVisibleCommand = createMockCommand(
|
|
||||||
'initially-visible',
|
|
||||||
CommandKind.BUILT_IN,
|
|
||||||
);
|
|
||||||
const hiddenOverrideCommand = {
|
|
||||||
...createMockCommand('initially-visible', CommandKind.FILE),
|
|
||||||
hidden: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockLoader = new MockCommandLoader([
|
|
||||||
visibleCommand,
|
|
||||||
hiddenCommand,
|
|
||||||
initiallyVisibleCommand,
|
|
||||||
hiddenOverrideCommand,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const service = await CommandService.create(
|
|
||||||
[mockLoader],
|
|
||||||
new AbortController().signal,
|
|
||||||
);
|
|
||||||
|
|
||||||
const commands = service.getCommands();
|
|
||||||
expect(commands).toHaveLength(1);
|
|
||||||
expect(commands[0].name).toBe('visible');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -85,9 +85,7 @@ export class CommandService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalCommands = Object.freeze(
|
const finalCommands = Object.freeze(Array.from(commandMap.values()));
|
||||||
Array.from(commandMap.values()).filter((cmd) => !cmd.hidden),
|
|
||||||
);
|
|
||||||
return new CommandService(finalCommands);
|
return new CommandService(finalCommands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import { CommandKind, type SlashCommand } from './types.js';
|
|||||||
export const corgiCommand: SlashCommand = {
|
export const corgiCommand: SlashCommand = {
|
||||||
name: 'corgi',
|
name: 'corgi',
|
||||||
description: 'Toggles corgi mode.',
|
description: 'Toggles corgi mode.',
|
||||||
kind: CommandKind.BUILT_IN,
|
|
||||||
hidden: true,
|
hidden: true,
|
||||||
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (context, _args) => {
|
action: (context, _args) => {
|
||||||
context.ui.toggleCorgiMode();
|
context.ui.toggleCorgiMode();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @vitest-environment jsdom */
|
||||||
|
|
||||||
|
import { render } from 'ink-testing-library';
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { Help } from './Help.js';
|
||||||
|
import type { SlashCommand } from '../commands/types.js';
|
||||||
|
import { CommandKind } from '../commands/types.js';
|
||||||
|
|
||||||
|
const mockCommands: readonly SlashCommand[] = [
|
||||||
|
{
|
||||||
|
name: 'test',
|
||||||
|
description: 'A test command',
|
||||||
|
kind: CommandKind.BUILT_IN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'hidden',
|
||||||
|
description: 'A hidden command',
|
||||||
|
hidden: true,
|
||||||
|
kind: CommandKind.BUILT_IN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parent',
|
||||||
|
description: 'A parent command',
|
||||||
|
kind: CommandKind.BUILT_IN,
|
||||||
|
subCommands: [
|
||||||
|
{
|
||||||
|
name: 'visible-child',
|
||||||
|
description: 'A visible child command',
|
||||||
|
kind: CommandKind.BUILT_IN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'hidden-child',
|
||||||
|
description: 'A hidden child command',
|
||||||
|
hidden: true,
|
||||||
|
kind: CommandKind.BUILT_IN,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('Help Component', () => {
|
||||||
|
it('should not render hidden commands', () => {
|
||||||
|
const { lastFrame } = render(<Help commands={mockCommands} />);
|
||||||
|
const output = lastFrame();
|
||||||
|
|
||||||
|
expect(output).toContain('/test');
|
||||||
|
expect(output).not.toContain('/hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render hidden subcommands', () => {
|
||||||
|
const { lastFrame } = render(<Help commands={mockCommands} />);
|
||||||
|
const output = lastFrame();
|
||||||
|
|
||||||
|
expect(output).toContain('visible-child');
|
||||||
|
expect(output).not.toContain('hidden-child');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -65,7 +65,7 @@ export const Help: React.FC<Help> = ({ commands }) => (
|
|||||||
Commands:
|
Commands:
|
||||||
</Text>
|
</Text>
|
||||||
{commands
|
{commands
|
||||||
.filter((command) => command.description)
|
.filter((command) => command.description && !command.hidden)
|
||||||
.map((command: SlashCommand) => (
|
.map((command: SlashCommand) => (
|
||||||
<Box key={command.name} flexDirection="column">
|
<Box key={command.name} flexDirection="column">
|
||||||
<Text color={Colors.Foreground}>
|
<Text color={Colors.Foreground}>
|
||||||
@@ -79,7 +79,9 @@ export const Help: React.FC<Help> = ({ commands }) => (
|
|||||||
{command.description && ' - ' + command.description}
|
{command.description && ' - ' + command.description}
|
||||||
</Text>
|
</Text>
|
||||||
{command.subCommands &&
|
{command.subCommands &&
|
||||||
command.subCommands.map((subCommand) => (
|
command.subCommands
|
||||||
|
.filter((subCommand) => !subCommand.hidden)
|
||||||
|
.map((subCommand) => (
|
||||||
<Text key={subCommand.name} color={Colors.Foreground}>
|
<Text key={subCommand.name} color={Colors.Foreground}>
|
||||||
<Text bold color={Colors.AccentPurple}>
|
<Text bold color={Colors.AccentPurple}>
|
||||||
{' '}
|
{' '}
|
||||||
|
|||||||
@@ -223,18 +223,6 @@ describe('useSlashCommandProcessor', () => {
|
|||||||
expect(fileAction).toHaveBeenCalledTimes(1);
|
expect(fileAction).toHaveBeenCalledTimes(1);
|
||||||
expect(builtinAction).not.toHaveBeenCalled();
|
expect(builtinAction).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not include hidden commands in the command list', async () => {
|
|
||||||
const visibleCommand = createTestCommand({ name: 'visible' });
|
|
||||||
const hiddenCommand = createTestCommand({ name: 'hidden', hidden: true });
|
|
||||||
const result = setupProcessorHook([visibleCommand, hiddenCommand]);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(result.current.slashCommands).toHaveLength(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.current.slashCommands[0].name).toBe('visible');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Command Execution Logic', () => {
|
describe('Command Execution Logic', () => {
|
||||||
|
|||||||
@@ -347,6 +347,31 @@ describe('useSlashCompletion', () => {
|
|||||||
|
|
||||||
expect(result.current.suggestions).toHaveLength(0);
|
expect(result.current.suggestions).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not suggest hidden commands', async () => {
|
||||||
|
const slashCommands = [
|
||||||
|
createTestCommand({
|
||||||
|
name: 'visible',
|
||||||
|
description: 'A visible command',
|
||||||
|
}),
|
||||||
|
createTestCommand({
|
||||||
|
name: 'hidden',
|
||||||
|
description: 'A hidden command',
|
||||||
|
hidden: true,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useTestHarnessForSlashCompletion(
|
||||||
|
true,
|
||||||
|
'/',
|
||||||
|
slashCommands,
|
||||||
|
mockCommandContext,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.current.suggestions.length).toBe(1);
|
||||||
|
expect(result.current.suggestions[0].label).toBe('visible');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Sub-Commands', () => {
|
describe('Sub-Commands', () => {
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ function useCommandSuggestions(
|
|||||||
if (partial === '') {
|
if (partial === '') {
|
||||||
// If no partial query, show all available commands
|
// If no partial query, show all available commands
|
||||||
potentialSuggestions = commandsToSearch.filter(
|
potentialSuggestions = commandsToSearch.filter(
|
||||||
(cmd) => cmd.description,
|
(cmd) => cmd.description && !cmd.hidden,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Use fuzzy search for non-empty partial queries with fallback
|
// Use fuzzy search for non-empty partial queries with fallback
|
||||||
@@ -400,7 +400,7 @@ export function useSlashCompletion(props: UseSlashCompletionProps): {
|
|||||||
const commandMap = new Map<string, SlashCommand>();
|
const commandMap = new Map<string, SlashCommand>();
|
||||||
|
|
||||||
commands.forEach((cmd) => {
|
commands.forEach((cmd) => {
|
||||||
if (cmd.description) {
|
if (cmd.description && !cmd.hidden) {
|
||||||
commandItems.push(cmd.name);
|
commandItems.push(cmd.name);
|
||||||
commandMap.set(cmd.name, cmd);
|
commandMap.set(cmd.name, cmd);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user