feat: Show /ide subcommands based on connection status instead of ideMode boolean (#6496)

This commit is contained in:
Shreya Keshive 2025-08-19 10:24:58 -07:00 committed by GitHub
parent fde5511c27
commit 9588aa6ef9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 60 additions and 12 deletions

View File

@ -162,6 +162,9 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
getIdeClient: vi.fn(() => ({
getCurrentIde: vi.fn(() => 'vscode'),
getDetectedIdeDisplayName: vi.fn(() => 'VSCode'),
addStatusChangeListener: vi.fn(),
removeStatusChangeListener: vi.fn(),
getConnectionStatus: vi.fn(() => 'connected'),
})),
isTrustedFolder: vi.fn(() => true),
};

View File

@ -69,16 +69,35 @@ describe('ideCommand', () => {
vi.mocked(mockConfig.getIdeClient).mockReturnValue({
getCurrentIde: () => DetectedIde.VSCode,
getDetectedIdeDisplayName: () => 'VS Code',
getConnectionStatus: () => ({
status: core.IDEConnectionStatus.Disconnected,
}),
} as ReturnType<Config['getIdeClient']>);
const command = ideCommand(mockConfig);
expect(command).not.toBeNull();
expect(command?.name).toBe('ide');
expect(command?.subCommands).toHaveLength(3);
expect(command?.subCommands?.[0].name).toBe('disable');
expect(command?.subCommands?.[0].name).toBe('enable');
expect(command?.subCommands?.[1].name).toBe('status');
expect(command?.subCommands?.[2].name).toBe('install');
});
it('should show disable command when connected', () => {
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
vi.mocked(mockConfig.getIdeClient).mockReturnValue({
getCurrentIde: () => DetectedIde.VSCode,
getDetectedIdeDisplayName: () => 'VS Code',
getConnectionStatus: () => ({
status: core.IDEConnectionStatus.Connected,
}),
} as ReturnType<Config['getIdeClient']>);
const command = ideCommand(mockConfig);
expect(command).not.toBeNull();
const subCommandNames = command?.subCommands?.map((cmd) => cmd.name);
expect(subCommandNames).toContain('disable');
expect(subCommandNames).not.toContain('enable');
});
describe('status subcommand', () => {
const mockGetConnectionStatus = vi.fn();
beforeEach(() => {
@ -161,7 +180,9 @@ describe('ideCommand', () => {
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
vi.mocked(mockConfig.getIdeClient).mockReturnValue({
getCurrentIde: () => DetectedIde.VSCode,
getConnectionStatus: vi.fn(),
getConnectionStatus: () => ({
status: core.IDEConnectionStatus.Disconnected,
}),
getDetectedIdeDisplayName: () => 'VS Code',
} as unknown as ReturnType<Config['getIdeClient']>);
vi.mocked(core.getIdeInstaller).mockReturnValue({

View File

@ -237,13 +237,11 @@ export const ideCommand = (config: Config | null): SlashCommand | null => {
},
};
const ideModeEnabled = config.getIdeMode();
if (ideModeEnabled) {
ideSlashCommand.subCommands = [
disableCommand,
statusCommand,
installCommand,
];
const { status } = ideClient.getConnectionStatus();
const isConnected = status === IDEConnectionStatus.Connected;
if (isConnected) {
ideSlashCommand.subCommands = [statusCommand, disableCommand];
} else {
ideSlashCommand.subCommands = [
enableCommand,

View File

@ -206,7 +206,22 @@ export const useSlashCommandProcessor = (
],
);
const ideMode = config?.getIdeMode();
useEffect(() => {
if (!config) {
return;
}
const ideClient = config.getIdeClient();
const listener = () => {
reloadCommands();
};
ideClient.addStatusChangeListener(listener);
return () => {
ideClient.removeStatusChangeListener(listener);
};
}, [config, reloadCommands]);
useEffect(() => {
const controller = new AbortController();
@ -228,7 +243,7 @@ export const useSlashCommandProcessor = (
return () => {
controller.abort();
};
}, [config, ideMode, reloadTrigger]);
}, [config, reloadTrigger]);
const handleSlashCommand = useCallback(
async (

View File

@ -63,6 +63,7 @@ export class IdeClient {
private readonly currentIde: DetectedIde | undefined;
private readonly currentIdeDisplayName: string | undefined;
private diffResponses = new Map<string, (result: DiffUpdateResult) => void>();
private statusListeners = new Set<(state: IDEConnectionState) => void>();
private constructor() {
this.currentIde = detectIde();
@ -78,6 +79,14 @@ export class IdeClient {
return IdeClient.instance;
}
addStatusChangeListener(listener: (state: IDEConnectionState) => void) {
this.statusListeners.add(listener);
}
removeStatusChangeListener(listener: (state: IDEConnectionState) => void) {
this.statusListeners.delete(listener);
}
async connect(): Promise<void> {
if (!this.currentIde || !this.currentIdeDisplayName) {
this.setState(
@ -237,6 +246,9 @@ export class IdeClient {
// disconnected, so that the first detail message is preserved.
if (!isAlreadyDisconnected) {
this.state = { status, details };
for (const listener of this.statusListeners) {
listener(this.state);
}
if (details) {
if (logToConsole) {
logger.error(details);
@ -390,7 +402,6 @@ export class IdeClient {
logger.debug('Failed to close transport:', closeError);
}
}
logger.error(`Failed to connect: ${_error}`);
return false;
}
}