Improve user-facing error messages for IDE mode (#5522)
This commit is contained in:
parent
11808ef7ed
commit
2180dd13dc
|
@ -65,6 +65,7 @@ describe('ideCommand', () => {
|
||||||
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
|
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
|
||||||
vi.mocked(mockConfig.getIdeClient).mockReturnValue({
|
vi.mocked(mockConfig.getIdeClient).mockReturnValue({
|
||||||
getCurrentIde: () => DetectedIde.VSCode,
|
getCurrentIde: () => DetectedIde.VSCode,
|
||||||
|
getDetectedIdeDisplayName: () => 'VS Code',
|
||||||
} as ReturnType<Config['getIdeClient']>);
|
} as ReturnType<Config['getIdeClient']>);
|
||||||
const command = ideCommand(mockConfig);
|
const command = ideCommand(mockConfig);
|
||||||
expect(command).not.toBeNull();
|
expect(command).not.toBeNull();
|
||||||
|
@ -82,6 +83,7 @@ describe('ideCommand', () => {
|
||||||
vi.mocked(mockConfig.getIdeClient).mockReturnValue({
|
vi.mocked(mockConfig.getIdeClient).mockReturnValue({
|
||||||
getConnectionStatus: mockGetConnectionStatus,
|
getConnectionStatus: mockGetConnectionStatus,
|
||||||
getCurrentIde: () => DetectedIde.VSCode,
|
getCurrentIde: () => DetectedIde.VSCode,
|
||||||
|
getDetectedIdeDisplayName: () => 'VS Code',
|
||||||
} as unknown as ReturnType<Config['getIdeClient']>);
|
} as unknown as ReturnType<Config['getIdeClient']>);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -96,7 +98,7 @@ describe('ideCommand', () => {
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'info',
|
messageType: 'info',
|
||||||
content: '🟢 Connected',
|
content: '🟢 Connected to VS Code',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -155,6 +157,7 @@ describe('ideCommand', () => {
|
||||||
vi.mocked(mockConfig.getIdeClient).mockReturnValue({
|
vi.mocked(mockConfig.getIdeClient).mockReturnValue({
|
||||||
getCurrentIde: () => DetectedIde.VSCode,
|
getCurrentIde: () => DetectedIde.VSCode,
|
||||||
getConnectionStatus: vi.fn(),
|
getConnectionStatus: vi.fn(),
|
||||||
|
getDetectedIdeDisplayName: () => 'VS Code',
|
||||||
} as unknown as ReturnType<Config['getIdeClient']>);
|
} as unknown as ReturnType<Config['getIdeClient']>);
|
||||||
vi.mocked(core.getIdeInstaller).mockReturnValue({
|
vi.mocked(core.getIdeInstaller).mockReturnValue({
|
||||||
install: mockInstall,
|
install: mockInstall,
|
||||||
|
@ -180,7 +183,7 @@ describe('ideCommand', () => {
|
||||||
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
text: `Installing IDE companion extension...`,
|
text: `Installing IDE companion...`,
|
||||||
}),
|
}),
|
||||||
expect.any(Number),
|
expect.any(Number),
|
||||||
);
|
);
|
||||||
|
@ -210,7 +213,7 @@ describe('ideCommand', () => {
|
||||||
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
text: `Installing IDE companion extension...`,
|
text: `Installing IDE companion...`,
|
||||||
}),
|
}),
|
||||||
expect.any(Number),
|
expect.any(Number),
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Config,
|
Config,
|
||||||
|
DetectedIde,
|
||||||
IDEConnectionStatus,
|
IDEConnectionStatus,
|
||||||
getIdeDisplayName,
|
getIdeDisplayName,
|
||||||
getIdeInstaller,
|
getIdeInstaller,
|
||||||
|
@ -19,12 +20,27 @@ import {
|
||||||
import { SettingScope } from '../../config/settings.js';
|
import { SettingScope } from '../../config/settings.js';
|
||||||
|
|
||||||
export const ideCommand = (config: Config | null): SlashCommand | null => {
|
export const ideCommand = (config: Config | null): SlashCommand | null => {
|
||||||
if (!config?.getIdeModeFeature()) {
|
if (!config || !config.getIdeModeFeature()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const currentIDE = config.getIdeClient().getCurrentIde();
|
const ideClient = config.getIdeClient();
|
||||||
if (!currentIDE) {
|
const currentIDE = ideClient.getCurrentIde();
|
||||||
return null;
|
if (!currentIDE || !ideClient.getDetectedIdeDisplayName()) {
|
||||||
|
return {
|
||||||
|
name: 'ide',
|
||||||
|
description: 'manage IDE integration',
|
||||||
|
kind: CommandKind.BUILT_IN,
|
||||||
|
action: (): SlashCommandActionReturn =>
|
||||||
|
({
|
||||||
|
type: 'message',
|
||||||
|
messageType: 'error',
|
||||||
|
content: `IDE integration is not supported in your current environment. To use this feature, run Gemini CLI in one of these supported IDEs: ${Object.values(
|
||||||
|
DetectedIde,
|
||||||
|
)
|
||||||
|
.map((ide) => getIdeDisplayName(ide))
|
||||||
|
.join(', ')}`,
|
||||||
|
}) as const,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const ideSlashCommand: SlashCommand = {
|
const ideSlashCommand: SlashCommand = {
|
||||||
|
@ -39,13 +55,13 @@ export const ideCommand = (config: Config | null): SlashCommand | null => {
|
||||||
description: 'check status of IDE integration',
|
description: 'check status of IDE integration',
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (_context: CommandContext): SlashCommandActionReturn => {
|
action: (_context: CommandContext): SlashCommandActionReturn => {
|
||||||
const connection = config.getIdeClient().getConnectionStatus();
|
const connection = ideClient.getConnectionStatus();
|
||||||
switch (connection?.status) {
|
switch (connection.status) {
|
||||||
case IDEConnectionStatus.Connected:
|
case IDEConnectionStatus.Connected:
|
||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'info',
|
messageType: 'info',
|
||||||
content: `🟢 Connected`,
|
content: `🟢 Connected to ${ideClient.getDetectedIdeDisplayName()}`,
|
||||||
} as const;
|
} as const;
|
||||||
case IDEConnectionStatus.Connecting:
|
case IDEConnectionStatus.Connecting:
|
||||||
return {
|
return {
|
||||||
|
@ -70,7 +86,7 @@ export const ideCommand = (config: Config | null): SlashCommand | null => {
|
||||||
|
|
||||||
const installCommand: SlashCommand = {
|
const installCommand: SlashCommand = {
|
||||||
name: 'install',
|
name: 'install',
|
||||||
description: `install required IDE companion ${getIdeDisplayName(currentIDE)} extension `,
|
description: `install required IDE companion for ${ideClient.getDetectedIdeDisplayName()}`,
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context) => {
|
action: async (context) => {
|
||||||
const installer = getIdeInstaller(currentIDE);
|
const installer = getIdeInstaller(currentIDE);
|
||||||
|
@ -78,7 +94,7 @@ export const ideCommand = (config: Config | null): SlashCommand | null => {
|
||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: 'error',
|
type: 'error',
|
||||||
text: 'No installer available for your configured IDE.',
|
text: `No installer is available for ${ideClient.getDetectedIdeDisplayName()}. Please install the IDE companion manually from its marketplace.`,
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
|
@ -88,7 +104,7 @@ export const ideCommand = (config: Config | null): SlashCommand | null => {
|
||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: 'info',
|
type: 'info',
|
||||||
text: `Installing IDE companion extension...`,
|
text: `Installing IDE companion...`,
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,9 +11,12 @@ export enum DetectedIde {
|
||||||
export function getIdeDisplayName(ide: DetectedIde): string {
|
export function getIdeDisplayName(ide: DetectedIde): string {
|
||||||
switch (ide) {
|
switch (ide) {
|
||||||
case DetectedIde.VSCode:
|
case DetectedIde.VSCode:
|
||||||
return 'VSCode';
|
return 'VS Code';
|
||||||
default:
|
default: {
|
||||||
throw new Error(`Unsupported IDE: ${ide}`);
|
// This ensures that if a new IDE is added to the enum, we get a compile-time error.
|
||||||
|
const exhaustiveCheck: never = ide;
|
||||||
|
return exhaustiveCheck;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,32 +45,6 @@ describe('ide-installer', () => {
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isInstalled', () => {
|
|
||||||
it('should return true if command is in PATH', async () => {
|
|
||||||
expect(await installer.isInstalled()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return true if command is in a known location', async () => {
|
|
||||||
vi.spyOn(child_process, 'execSync').mockImplementation(() => {
|
|
||||||
throw new Error('Command not found');
|
|
||||||
});
|
|
||||||
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
||||||
// Re-create the installer so it re-runs findVsCodeCommand
|
|
||||||
installer = getIdeInstaller(DetectedIde.VSCode)!;
|
|
||||||
expect(await installer.isInstalled()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false if command is not found', async () => {
|
|
||||||
vi.spyOn(child_process, 'execSync').mockImplementation(() => {
|
|
||||||
throw new Error('Command not found');
|
|
||||||
});
|
|
||||||
vi.spyOn(fs, 'existsSync').mockReturnValue(false);
|
|
||||||
// Re-create the installer so it re-runs findVsCodeCommand
|
|
||||||
installer = getIdeInstaller(DetectedIde.VSCode)!;
|
|
||||||
expect(await installer.isInstalled()).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('install', () => {
|
describe('install', () => {
|
||||||
it('should return a failure message if VS Code is not installed', async () => {
|
it('should return a failure message if VS Code is not installed', async () => {
|
||||||
vi.spyOn(child_process, 'execSync').mockImplementation(() => {
|
vi.spyOn(child_process, 'execSync').mockImplementation(() => {
|
||||||
|
@ -81,9 +55,7 @@ describe('ide-installer', () => {
|
||||||
installer = getIdeInstaller(DetectedIde.VSCode)!;
|
installer = getIdeInstaller(DetectedIde.VSCode)!;
|
||||||
const result = await installer.install();
|
const result = await installer.install();
|
||||||
expect(result.success).toBe(false);
|
expect(result.success).toBe(false);
|
||||||
expect(result.message).toContain(
|
expect(result.message).toContain('VS Code CLI not found');
|
||||||
'not found in your PATH or common installation locations',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,7 +18,6 @@ const VSCODE_COMPANION_EXTENSION_FOLDER = 'vscode-ide-companion';
|
||||||
|
|
||||||
export interface IdeInstaller {
|
export interface IdeInstaller {
|
||||||
install(): Promise<InstallResult>;
|
install(): Promise<InstallResult>;
|
||||||
isInstalled(): Promise<boolean>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InstallResult {
|
export interface InstallResult {
|
||||||
|
@ -95,16 +94,12 @@ class VsCodeInstaller implements IdeInstaller {
|
||||||
this.vsCodeCommand = findVsCodeCommand();
|
this.vsCodeCommand = findVsCodeCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
async isInstalled(): Promise<boolean> {
|
|
||||||
return (await this.vsCodeCommand) !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async install(): Promise<InstallResult> {
|
async install(): Promise<InstallResult> {
|
||||||
const commandPath = await this.vsCodeCommand;
|
const commandPath = await this.vsCodeCommand;
|
||||||
if (!commandPath) {
|
if (!commandPath) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: `VS Code command-line tool not found in your PATH or common installation locations.`,
|
message: `VS Code CLI not found. Please ensure 'code' is in your system's PATH. For help, see https://code.visualstudio.com/docs/configure/command-line#_code-is-not-recognized-as-an-internal-or-external-command. You can also install the companion extension manually from the VS Code marketplace.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,12 +136,12 @@ class VsCodeInstaller implements IdeInstaller {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message:
|
message:
|
||||||
'VS Code companion extension installed successfully. Restart gemini-cli in a fresh terminal window.',
|
'VS Code companion extension was installed successfully. Please restart your terminal to complete the setup.',
|
||||||
};
|
};
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Failed to install VS Code companion extension.',
|
message: `Failed to install VS Code companion extension. Please try installing it manually from the VS Code marketplace.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,7 +149,7 @@ class VsCodeInstaller implements IdeInstaller {
|
||||||
|
|
||||||
export function getIdeInstaller(ide: DetectedIde): IdeInstaller | null {
|
export function getIdeInstaller(ide: DetectedIde): IdeInstaller | null {
|
||||||
switch (ide) {
|
switch (ide) {
|
||||||
case 'vscode':
|
case DetectedIde.VSCode:
|
||||||
return new VsCodeInstaller();
|
return new VsCodeInstaller();
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
|
Loading…
Reference in New Issue