Add toggleable IDE mode setting (#5146)
This commit is contained in:
parent
ac1bb5ee42
commit
325bb89137
|
@ -916,7 +916,7 @@ describe('loadCliConfig extensions', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loadCliConfig ideMode', () => {
|
describe('loadCliConfig ideModeFeature', () => {
|
||||||
const originalArgv = process.argv;
|
const originalArgv = process.argv;
|
||||||
const originalEnv = { ...process.env };
|
const originalEnv = { ...process.env };
|
||||||
|
|
||||||
|
@ -939,16 +939,16 @@ describe('loadCliConfig ideMode', () => {
|
||||||
const settings: Settings = {};
|
const settings: Settings = {};
|
||||||
const argv = await parseArguments();
|
const argv = await parseArguments();
|
||||||
const config = await loadCliConfig(settings, [], 'test-session', argv);
|
const config = await loadCliConfig(settings, [], 'test-session', argv);
|
||||||
expect(config.getIdeMode()).toBe(false);
|
expect(config.getIdeModeFeature()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be false when settings.ideMode is true, but SANDBOX is set', async () => {
|
it('should be false when settings.ideModeFeature is true, but SANDBOX is set', async () => {
|
||||||
process.argv = ['node', 'script.js'];
|
process.argv = ['node', 'script.js'];
|
||||||
const argv = await parseArguments();
|
const argv = await parseArguments();
|
||||||
process.env.TERM_PROGRAM = 'vscode';
|
process.env.TERM_PROGRAM = 'vscode';
|
||||||
process.env.SANDBOX = 'true';
|
process.env.SANDBOX = 'true';
|
||||||
const settings: Settings = { ideMode: true };
|
const settings: Settings = { ideModeFeature: true };
|
||||||
const config = await loadCliConfig(settings, [], 'test-session', argv);
|
const config = await loadCliConfig(settings, [], 'test-session', argv);
|
||||||
expect(config.getIdeMode()).toBe(false);
|
expect(config.getIdeModeFeature()).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -59,7 +59,7 @@ export interface CliArgs {
|
||||||
experimentalAcp: boolean | undefined;
|
experimentalAcp: boolean | undefined;
|
||||||
extensions: string[] | undefined;
|
extensions: string[] | undefined;
|
||||||
listExtensions: boolean | undefined;
|
listExtensions: boolean | undefined;
|
||||||
ideMode: boolean | undefined;
|
ideModeFeature: boolean | undefined;
|
||||||
proxy: string | undefined;
|
proxy: string | undefined;
|
||||||
includeDirectories: string[] | undefined;
|
includeDirectories: string[] | undefined;
|
||||||
}
|
}
|
||||||
|
@ -191,7 +191,7 @@ export async function parseArguments(): Promise<CliArgs> {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: 'List all available extensions and exit.',
|
description: 'List all available extensions and exit.',
|
||||||
})
|
})
|
||||||
.option('ide-mode', {
|
.option('ide-mode-feature', {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: 'Run in IDE mode?',
|
description: 'Run in IDE mode?',
|
||||||
})
|
})
|
||||||
|
@ -268,10 +268,13 @@ export async function loadCliConfig(
|
||||||
(v) => v === 'true' || v === '1',
|
(v) => v === 'true' || v === '1',
|
||||||
);
|
);
|
||||||
|
|
||||||
const ideMode =
|
const ideMode = settings.ideMode ?? false;
|
||||||
(argv.ideMode ?? settings.ideMode ?? false) && !process.env.SANDBOX;
|
|
||||||
|
|
||||||
const ideClient = IdeClient.getInstance(ideMode);
|
const ideModeFeature =
|
||||||
|
(argv.ideModeFeature ?? settings.ideModeFeature ?? false) &&
|
||||||
|
!process.env.SANDBOX;
|
||||||
|
|
||||||
|
const ideClient = IdeClient.getInstance(ideMode && ideModeFeature);
|
||||||
|
|
||||||
const allExtensions = annotateActiveExtensions(
|
const allExtensions = annotateActiveExtensions(
|
||||||
extensions,
|
extensions,
|
||||||
|
@ -429,6 +432,7 @@ export async function loadCliConfig(
|
||||||
noBrowser: !!process.env.NO_BROWSER,
|
noBrowser: !!process.env.NO_BROWSER,
|
||||||
summarizeToolOutput: settings.summarizeToolOutput,
|
summarizeToolOutput: settings.summarizeToolOutput,
|
||||||
ideMode,
|
ideMode,
|
||||||
|
ideModeFeature,
|
||||||
ideClient,
|
ideClient,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,9 @@ export interface Settings {
|
||||||
|
|
||||||
vimMode?: boolean;
|
vimMode?: boolean;
|
||||||
|
|
||||||
// Add other settings here.
|
// Flag to be removed post-launch.
|
||||||
|
ideModeFeature?: boolean;
|
||||||
|
/// IDE mode setting configured via slash command toggle.
|
||||||
ideMode?: boolean;
|
ideMode?: boolean;
|
||||||
|
|
||||||
// Setting for disabling auto-update.
|
// Setting for disabling auto-update.
|
||||||
|
|
|
@ -151,6 +151,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||||
setFlashFallbackHandler: vi.fn(),
|
setFlashFallbackHandler: vi.fn(),
|
||||||
getSessionId: vi.fn(() => 'test-session-id'),
|
getSessionId: vi.fn(() => 'test-session-id'),
|
||||||
getUserTier: vi.fn().mockResolvedValue(undefined),
|
getUserTier: vi.fn().mockResolvedValue(undefined),
|
||||||
|
getIdeModeFeature: vi.fn(() => false),
|
||||||
getIdeMode: vi.fn(() => false),
|
getIdeMode: vi.fn(() => false),
|
||||||
getWorkspaceContext: vi.fn(() => ({
|
getWorkspaceContext: vi.fn(() => ({
|
||||||
getDirectories: vi.fn(() => []),
|
getDirectories: vi.fn(() => []),
|
||||||
|
|
|
@ -573,7 +573,12 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
||||||
if (Object.keys(mcpServers || {}).length > 0) {
|
if (Object.keys(mcpServers || {}).length > 0) {
|
||||||
handleSlashCommand(newValue ? '/mcp desc' : '/mcp nodesc');
|
handleSlashCommand(newValue ? '/mcp desc' : '/mcp nodesc');
|
||||||
}
|
}
|
||||||
} else if (key.ctrl && input === 'e' && ideContextState) {
|
} else if (
|
||||||
|
key.ctrl &&
|
||||||
|
input === 'e' &&
|
||||||
|
config.getIdeMode() &&
|
||||||
|
ideContextState
|
||||||
|
) {
|
||||||
setShowIDEContextDetail((prev) => !prev);
|
setShowIDEContextDetail((prev) => !prev);
|
||||||
} else if (key.ctrl && (input === 'c' || input === 'C')) {
|
} else if (key.ctrl && (input === 'c' || input === 'C')) {
|
||||||
handleExit(ctrlCPressedOnce, setCtrlCPressedOnce, ctrlCTimerRef);
|
handleExit(ctrlCPressedOnce, setCtrlCPressedOnce, ctrlCTimerRef);
|
||||||
|
|
|
@ -32,11 +32,19 @@ describe('ideCommand', () => {
|
||||||
ui: {
|
ui: {
|
||||||
addItem: vi.fn(),
|
addItem: vi.fn(),
|
||||||
},
|
},
|
||||||
|
services: {
|
||||||
|
settings: {
|
||||||
|
setValue: vi.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
} as unknown as CommandContext;
|
} as unknown as CommandContext;
|
||||||
|
|
||||||
mockConfig = {
|
mockConfig = {
|
||||||
|
getIdeModeFeature: vi.fn(),
|
||||||
getIdeMode: vi.fn(),
|
getIdeMode: vi.fn(),
|
||||||
getIdeClient: vi.fn(),
|
getIdeClient: vi.fn(),
|
||||||
|
setIdeMode: vi.fn(),
|
||||||
|
setIdeClientDisconnected: vi.fn(),
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
platformSpy = vi.spyOn(process, 'platform', 'get');
|
platformSpy = vi.spyOn(process, 'platform', 'get');
|
||||||
|
@ -46,13 +54,14 @@ describe('ideCommand', () => {
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null if ideMode is not enabled', () => {
|
it('should return null if ideModeFeature is not enabled', () => {
|
||||||
vi.mocked(mockConfig.getIdeMode).mockReturnValue(false);
|
vi.mocked(mockConfig.getIdeModeFeature).mockReturnValue(false);
|
||||||
const command = ideCommand(mockConfig);
|
const command = ideCommand(mockConfig);
|
||||||
expect(command).toBeNull();
|
expect(command).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the ide command if ideMode is enabled', () => {
|
it('should return the ide command if ideModeFeature is enabled', () => {
|
||||||
|
vi.mocked(mockConfig.getIdeModeFeature).mockReturnValue(true);
|
||||||
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,
|
||||||
|
@ -60,19 +69,20 @@ describe('ideCommand', () => {
|
||||||
const command = ideCommand(mockConfig);
|
const command = ideCommand(mockConfig);
|
||||||
expect(command).not.toBeNull();
|
expect(command).not.toBeNull();
|
||||||
expect(command?.name).toBe('ide');
|
expect(command?.name).toBe('ide');
|
||||||
expect(command?.subCommands).toHaveLength(2);
|
expect(command?.subCommands).toHaveLength(3);
|
||||||
expect(command?.subCommands?.[0].name).toBe('status');
|
expect(command?.subCommands?.[0].name).toBe('disable');
|
||||||
expect(command?.subCommands?.[1].name).toBe('install');
|
expect(command?.subCommands?.[1].name).toBe('status');
|
||||||
|
expect(command?.subCommands?.[2].name).toBe('install');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('status subcommand', () => {
|
describe('status subcommand', () => {
|
||||||
const mockGetConnectionStatus = vi.fn();
|
const mockGetConnectionStatus = vi.fn();
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
|
vi.mocked(mockConfig.getIdeModeFeature).mockReturnValue(true);
|
||||||
vi.mocked(mockConfig.getIdeClient).mockReturnValue({
|
vi.mocked(mockConfig.getIdeClient).mockReturnValue({
|
||||||
getConnectionStatus: mockGetConnectionStatus,
|
getConnectionStatus: mockGetConnectionStatus,
|
||||||
getCurrentIde: () => DetectedIde.VSCode,
|
getCurrentIde: () => DetectedIde.VSCode,
|
||||||
} as ReturnType<Config['getIdeClient']>);
|
} as unknown as ReturnType<Config['getIdeClient']>);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show connected status', () => {
|
it('should show connected status', () => {
|
||||||
|
@ -80,7 +90,8 @@ describe('ideCommand', () => {
|
||||||
status: core.IDEConnectionStatus.Connected,
|
status: core.IDEConnectionStatus.Connected,
|
||||||
});
|
});
|
||||||
const command = ideCommand(mockConfig);
|
const command = ideCommand(mockConfig);
|
||||||
const result = command!.subCommands![0].action!(mockContext, '');
|
const result = command!.subCommands!.find((c) => c.name === 'status')!
|
||||||
|
.action!(mockContext, '');
|
||||||
expect(mockGetConnectionStatus).toHaveBeenCalled();
|
expect(mockGetConnectionStatus).toHaveBeenCalled();
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
type: 'message',
|
type: 'message',
|
||||||
|
@ -94,7 +105,8 @@ describe('ideCommand', () => {
|
||||||
status: core.IDEConnectionStatus.Connecting,
|
status: core.IDEConnectionStatus.Connecting,
|
||||||
});
|
});
|
||||||
const command = ideCommand(mockConfig);
|
const command = ideCommand(mockConfig);
|
||||||
const result = command!.subCommands![0].action!(mockContext, '');
|
const result = command!.subCommands!.find((c) => c.name === 'status')!
|
||||||
|
.action!(mockContext, '');
|
||||||
expect(mockGetConnectionStatus).toHaveBeenCalled();
|
expect(mockGetConnectionStatus).toHaveBeenCalled();
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
type: 'message',
|
type: 'message',
|
||||||
|
@ -107,7 +119,8 @@ describe('ideCommand', () => {
|
||||||
status: core.IDEConnectionStatus.Disconnected,
|
status: core.IDEConnectionStatus.Disconnected,
|
||||||
});
|
});
|
||||||
const command = ideCommand(mockConfig);
|
const command = ideCommand(mockConfig);
|
||||||
const result = command!.subCommands![0].action!(mockContext, '');
|
const result = command!.subCommands!.find((c) => c.name === 'status')!
|
||||||
|
.action!(mockContext, '');
|
||||||
expect(mockGetConnectionStatus).toHaveBeenCalled();
|
expect(mockGetConnectionStatus).toHaveBeenCalled();
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
type: 'message',
|
type: 'message',
|
||||||
|
@ -123,7 +136,8 @@ describe('ideCommand', () => {
|
||||||
details,
|
details,
|
||||||
});
|
});
|
||||||
const command = ideCommand(mockConfig);
|
const command = ideCommand(mockConfig);
|
||||||
const result = command!.subCommands![0].action!(mockContext, '');
|
const result = command!.subCommands!.find((c) => c.name === 'status')!
|
||||||
|
.action!(mockContext, '');
|
||||||
expect(mockGetConnectionStatus).toHaveBeenCalled();
|
expect(mockGetConnectionStatus).toHaveBeenCalled();
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
type: 'message',
|
type: 'message',
|
||||||
|
@ -136,10 +150,12 @@ describe('ideCommand', () => {
|
||||||
describe('install subcommand', () => {
|
describe('install subcommand', () => {
|
||||||
const mockInstall = vi.fn();
|
const mockInstall = vi.fn();
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
vi.mocked(mockConfig.getIdeModeFeature).mockReturnValue(true);
|
||||||
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,
|
||||||
} as ReturnType<Config['getIdeClient']>);
|
getConnectionStatus: vi.fn(),
|
||||||
|
} as unknown as ReturnType<Config['getIdeClient']>);
|
||||||
vi.mocked(core.getIdeInstaller).mockReturnValue({
|
vi.mocked(core.getIdeInstaller).mockReturnValue({
|
||||||
install: mockInstall,
|
install: mockInstall,
|
||||||
isInstalled: vi.fn(),
|
isInstalled: vi.fn(),
|
||||||
|
@ -154,7 +170,10 @@ describe('ideCommand', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const command = ideCommand(mockConfig);
|
const command = ideCommand(mockConfig);
|
||||||
await command!.subCommands![1].action!(mockContext, '');
|
await command!.subCommands!.find((c) => c.name === 'install')!.action!(
|
||||||
|
mockContext,
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
|
||||||
expect(core.getIdeInstaller).toHaveBeenCalledWith('vscode');
|
expect(core.getIdeInstaller).toHaveBeenCalledWith('vscode');
|
||||||
expect(mockInstall).toHaveBeenCalled();
|
expect(mockInstall).toHaveBeenCalled();
|
||||||
|
@ -181,7 +200,10 @@ describe('ideCommand', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const command = ideCommand(mockConfig);
|
const command = ideCommand(mockConfig);
|
||||||
await command!.subCommands![1].action!(mockContext, '');
|
await command!.subCommands!.find((c) => c.name === 'install')!.action!(
|
||||||
|
mockContext,
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
|
||||||
expect(core.getIdeInstaller).toHaveBeenCalledWith('vscode');
|
expect(core.getIdeInstaller).toHaveBeenCalledWith('vscode');
|
||||||
expect(mockInstall).toHaveBeenCalled();
|
expect(mockInstall).toHaveBeenCalled();
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Config,
|
Config,
|
||||||
|
IDEConnectionStatus,
|
||||||
getIdeDisplayName,
|
getIdeDisplayName,
|
||||||
getIdeInstaller,
|
getIdeInstaller,
|
||||||
IDEConnectionStatus,
|
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import {
|
import {
|
||||||
CommandContext,
|
CommandContext,
|
||||||
|
@ -16,24 +16,25 @@ import {
|
||||||
SlashCommandActionReturn,
|
SlashCommandActionReturn,
|
||||||
CommandKind,
|
CommandKind,
|
||||||
} from './types.js';
|
} from './types.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?.getIdeMode()) {
|
if (!config?.getIdeModeFeature()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const currentIDE = config.getIdeClient().getCurrentIde();
|
const currentIDE = config.getIdeClient().getCurrentIde();
|
||||||
if (!currentIDE) {
|
if (!currentIDE) {
|
||||||
throw new Error(
|
return null;
|
||||||
'IDE slash command should not be available if not running in an IDE',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const ideSlashCommand: SlashCommand = {
|
||||||
name: 'ide',
|
name: 'ide',
|
||||||
description: 'manage IDE integration',
|
description: 'manage IDE integration',
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
subCommands: [
|
subCommands: [],
|
||||||
{
|
};
|
||||||
|
|
||||||
|
const statusCommand: SlashCommand = {
|
||||||
name: 'status',
|
name: 'status',
|
||||||
description: 'check status of IDE integration',
|
description: 'check status of IDE integration',
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
|
@ -65,8 +66,9 @@ export const ideCommand = (config: Config | null): SlashCommand | null => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
{
|
|
||||||
|
const installCommand: SlashCommand = {
|
||||||
name: 'install',
|
name: 'install',
|
||||||
description: `install required IDE companion ${getIdeDisplayName(currentIDE)} extension `,
|
description: `install required IDE companion ${getIdeDisplayName(currentIDE)} extension `,
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
|
@ -100,7 +102,44 @@ export const ideCommand = (config: Config | null): SlashCommand | null => {
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const enableCommand: SlashCommand = {
|
||||||
|
name: 'enable',
|
||||||
|
description: 'enable IDE integration',
|
||||||
|
kind: CommandKind.BUILT_IN,
|
||||||
|
action: async (context: CommandContext) => {
|
||||||
|
context.services.settings.setValue(SettingScope.User, 'ideMode', true);
|
||||||
|
config.setIdeMode(true);
|
||||||
|
config.setIdeClientConnected();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const disableCommand: SlashCommand = {
|
||||||
|
name: 'disable',
|
||||||
|
description: 'disable IDE integration',
|
||||||
|
kind: CommandKind.BUILT_IN,
|
||||||
|
action: async (context: CommandContext) => {
|
||||||
|
context.services.settings.setValue(SettingScope.User, 'ideMode', false);
|
||||||
|
config.setIdeMode(false);
|
||||||
|
config.setIdeClientDisconnected();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const ideModeEnabled = config.getIdeMode();
|
||||||
|
if (ideModeEnabled) {
|
||||||
|
ideSlashCommand.subCommands = [
|
||||||
|
disableCommand,
|
||||||
|
statusCommand,
|
||||||
|
installCommand,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
ideSlashCommand.subCommands = [
|
||||||
|
enableCommand,
|
||||||
|
statusCommand,
|
||||||
|
installCommand,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ideSlashCommand;
|
||||||
};
|
};
|
||||||
|
|
|
@ -101,6 +101,7 @@ describe('useSlashCommandProcessor', () => {
|
||||||
setHistory: vi.fn().mockResolvedValue(undefined),
|
setHistory: vi.fn().mockResolvedValue(undefined),
|
||||||
})),
|
})),
|
||||||
getExtensions: vi.fn(() => []),
|
getExtensions: vi.fn(() => []),
|
||||||
|
getIdeMode: vi.fn(() => false),
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
const mockSettings = {} as LoadedSettings;
|
const mockSettings = {} as LoadedSettings;
|
||||||
|
|
|
@ -185,6 +185,8 @@ export const useSlashCommandProcessor = (
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ideMode = config?.getIdeMode();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
|
@ -205,7 +207,7 @@ export const useSlashCommandProcessor = (
|
||||||
return () => {
|
return () => {
|
||||||
controller.abort();
|
controller.abort();
|
||||||
};
|
};
|
||||||
}, [config]);
|
}, [config, ideMode]);
|
||||||
|
|
||||||
const handleSlashCommand = useCallback(
|
const handleSlashCommand = useCallback(
|
||||||
async (
|
async (
|
||||||
|
|
|
@ -184,6 +184,7 @@ export interface ConfigParameters {
|
||||||
blockedMcpServers?: Array<{ name: string; extensionName: string }>;
|
blockedMcpServers?: Array<{ name: string; extensionName: string }>;
|
||||||
noBrowser?: boolean;
|
noBrowser?: boolean;
|
||||||
summarizeToolOutput?: Record<string, SummarizeToolOutputSettings>;
|
summarizeToolOutput?: Record<string, SummarizeToolOutputSettings>;
|
||||||
|
ideModeFeature?: boolean;
|
||||||
ideMode?: boolean;
|
ideMode?: boolean;
|
||||||
ideClient: IdeClient;
|
ideClient: IdeClient;
|
||||||
}
|
}
|
||||||
|
@ -228,8 +229,9 @@ export class Config {
|
||||||
private readonly model: string;
|
private readonly model: string;
|
||||||
private readonly extensionContextFilePaths: string[];
|
private readonly extensionContextFilePaths: string[];
|
||||||
private readonly noBrowser: boolean;
|
private readonly noBrowser: boolean;
|
||||||
private readonly ideMode: boolean;
|
private readonly ideModeFeature: boolean;
|
||||||
private readonly ideClient: IdeClient;
|
private ideMode: boolean;
|
||||||
|
private ideClient: IdeClient;
|
||||||
private inFallbackMode = false;
|
private inFallbackMode = false;
|
||||||
private readonly maxSessionTurns: number;
|
private readonly maxSessionTurns: number;
|
||||||
private readonly listExtensions: boolean;
|
private readonly listExtensions: boolean;
|
||||||
|
@ -298,7 +300,8 @@ export class Config {
|
||||||
this._blockedMcpServers = params.blockedMcpServers ?? [];
|
this._blockedMcpServers = params.blockedMcpServers ?? [];
|
||||||
this.noBrowser = params.noBrowser ?? false;
|
this.noBrowser = params.noBrowser ?? false;
|
||||||
this.summarizeToolOutput = params.summarizeToolOutput;
|
this.summarizeToolOutput = params.summarizeToolOutput;
|
||||||
this.ideMode = params.ideMode ?? false;
|
this.ideModeFeature = params.ideModeFeature ?? false;
|
||||||
|
this.ideMode = params.ideMode ?? true;
|
||||||
this.ideClient = params.ideClient;
|
this.ideClient = params.ideClient;
|
||||||
|
|
||||||
if (params.contextFileName) {
|
if (params.contextFileName) {
|
||||||
|
@ -589,14 +592,30 @@ export class Config {
|
||||||
return this.summarizeToolOutput;
|
return this.summarizeToolOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
getIdeMode(): boolean {
|
getIdeModeFeature(): boolean {
|
||||||
return this.ideMode;
|
return this.ideModeFeature;
|
||||||
}
|
}
|
||||||
|
|
||||||
getIdeClient(): IdeClient {
|
getIdeClient(): IdeClient {
|
||||||
return this.ideClient;
|
return this.ideClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getIdeMode(): boolean {
|
||||||
|
return this.ideMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIdeMode(value: boolean): void {
|
||||||
|
this.ideMode = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIdeClientDisconnected(): void {
|
||||||
|
this.ideClient.setDisconnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
setIdeClientConnected(): void {
|
||||||
|
this.ideClient.reconnect(this.ideMode && this.ideModeFeature);
|
||||||
|
}
|
||||||
|
|
||||||
async getGitService(): Promise<GitService> {
|
async getGitService(): Promise<GitService> {
|
||||||
if (!this.gitService) {
|
if (!this.gitService) {
|
||||||
this.gitService = new GitService(this.targetDir);
|
this.gitService = new GitService(this.targetDir);
|
||||||
|
|
|
@ -199,7 +199,8 @@ describe('Gemini Client (client.ts)', () => {
|
||||||
setQuotaErrorOccurred: vi.fn(),
|
setQuotaErrorOccurred: vi.fn(),
|
||||||
getNoBrowser: vi.fn().mockReturnValue(false),
|
getNoBrowser: vi.fn().mockReturnValue(false),
|
||||||
getUsageStatisticsEnabled: vi.fn().mockReturnValue(true),
|
getUsageStatisticsEnabled: vi.fn().mockReturnValue(true),
|
||||||
getIdeMode: vi.fn().mockReturnValue(false),
|
getIdeModeFeature: vi.fn().mockReturnValue(false),
|
||||||
|
getIdeMode: vi.fn().mockReturnValue(true),
|
||||||
getWorkspaceContext: vi.fn().mockReturnValue({
|
getWorkspaceContext: vi.fn().mockReturnValue({
|
||||||
getDirectories: vi.fn().mockReturnValue(['/test/dir']),
|
getDirectories: vi.fn().mockReturnValue(['/test/dir']),
|
||||||
}),
|
}),
|
||||||
|
@ -649,7 +650,7 @@ describe('Gemini Client (client.ts)', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('sendMessageStream', () => {
|
describe('sendMessageStream', () => {
|
||||||
it('should include IDE context when ideMode is enabled', async () => {
|
it('should include IDE context when ideModeFeature is enabled', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
vi.mocked(ideContext.getIdeContext).mockReturnValue({
|
vi.mocked(ideContext.getIdeContext).mockReturnValue({
|
||||||
workspaceState: {
|
workspaceState: {
|
||||||
|
@ -673,7 +674,7 @@ describe('Gemini Client (client.ts)', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
|
vi.spyOn(client['config'], 'getIdeModeFeature').mockReturnValue(true);
|
||||||
|
|
||||||
const mockStream = (async function* () {
|
const mockStream = (async function* () {
|
||||||
yield { type: 'content', value: 'Hello' };
|
yield { type: 'content', value: 'Hello' };
|
||||||
|
@ -724,7 +725,7 @@ Here are some other files the user has open, with the most recent at the top:
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not add context if ideMode is enabled but no open files', async () => {
|
it('should not add context if ideModeFeature is enabled but no open files', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
vi.mocked(ideContext.getIdeContext).mockReturnValue({
|
vi.mocked(ideContext.getIdeContext).mockReturnValue({
|
||||||
workspaceState: {
|
workspaceState: {
|
||||||
|
@ -732,7 +733,7 @@ Here are some other files the user has open, with the most recent at the top:
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
|
vi.spyOn(client['config'], 'getIdeModeFeature').mockReturnValue(true);
|
||||||
|
|
||||||
const mockStream = (async function* () {
|
const mockStream = (async function* () {
|
||||||
yield { type: 'content', value: 'Hello' };
|
yield { type: 'content', value: 'Hello' };
|
||||||
|
@ -771,7 +772,7 @@ Here are some other files the user has open, with the most recent at the top:
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add context if ideMode is enabled and there is one active file', async () => {
|
it('should add context if ideModeFeature is enabled and there is one active file', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
vi.mocked(ideContext.getIdeContext).mockReturnValue({
|
vi.mocked(ideContext.getIdeContext).mockReturnValue({
|
||||||
workspaceState: {
|
workspaceState: {
|
||||||
|
@ -787,7 +788,7 @@ Here are some other files the user has open, with the most recent at the top:
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
|
vi.spyOn(client['config'], 'getIdeModeFeature').mockReturnValue(true);
|
||||||
|
|
||||||
const mockStream = (async function* () {
|
const mockStream = (async function* () {
|
||||||
yield { type: 'content', value: 'Hello' };
|
yield { type: 'content', value: 'Hello' };
|
||||||
|
@ -835,7 +836,7 @@ This is the selected text in the file:
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add context if ideMode is enabled and there are open files but no active file', async () => {
|
it('should add context if ideModeFeature is enabled and there are open files but no active file', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
vi.mocked(ideContext.getIdeContext).mockReturnValue({
|
vi.mocked(ideContext.getIdeContext).mockReturnValue({
|
||||||
workspaceState: {
|
workspaceState: {
|
||||||
|
@ -852,7 +853,7 @@ This is the selected text in the file:
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
|
vi.spyOn(client['config'], 'getIdeModeFeature').mockReturnValue(true);
|
||||||
|
|
||||||
const mockStream = (async function* () {
|
const mockStream = (async function* () {
|
||||||
yield { type: 'content', value: 'Hello' };
|
yield { type: 'content', value: 'Hello' };
|
||||||
|
|
|
@ -339,7 +339,7 @@ export class GeminiClient {
|
||||||
yield { type: GeminiEventType.ChatCompressed, value: compressed };
|
yield { type: GeminiEventType.ChatCompressed, value: compressed };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.config.getIdeMode()) {
|
if (this.config.getIdeModeFeature() && this.config.getIdeMode()) {
|
||||||
const ideContextState = ideContext.getIdeContext();
|
const ideContextState = ideContext.getIdeContext();
|
||||||
const openFiles = ideContextState?.workspaceState?.openFiles;
|
const openFiles = ideContextState?.workspaceState?.openFiles;
|
||||||
|
|
||||||
|
|
|
@ -41,14 +41,14 @@ export class IdeClient {
|
||||||
private readonly currentIde: DetectedIde | undefined;
|
private readonly currentIde: DetectedIde | undefined;
|
||||||
private readonly currentIdeDisplayName: string | undefined;
|
private readonly currentIdeDisplayName: string | undefined;
|
||||||
|
|
||||||
private constructor(ideMode: boolean) {
|
constructor(ideMode: boolean) {
|
||||||
if (!ideMode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.currentIde = detectIde();
|
this.currentIde = detectIde();
|
||||||
if (this.currentIde) {
|
if (this.currentIde) {
|
||||||
this.currentIdeDisplayName = getIdeDisplayName(this.currentIde);
|
this.currentIdeDisplayName = getIdeDisplayName(this.currentIde);
|
||||||
}
|
}
|
||||||
|
if (!ideMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.init().catch((err) => {
|
this.init().catch((err) => {
|
||||||
logger.debug('Failed to initialize IdeClient:', err);
|
logger.debug('Failed to initialize IdeClient:', err);
|
||||||
});
|
});
|
||||||
|
@ -130,6 +130,10 @@ export class IdeClient {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async reconnect(ideMode: boolean) {
|
||||||
|
IdeClient.instance = new IdeClient(ideMode);
|
||||||
|
}
|
||||||
|
|
||||||
private async establishConnection(port: string) {
|
private async establishConnection(port: string) {
|
||||||
let transport: StreamableHTTPClientTransport | undefined;
|
let transport: StreamableHTTPClientTransport | undefined;
|
||||||
try {
|
try {
|
||||||
|
@ -189,7 +193,15 @@ export class IdeClient {
|
||||||
await this.establishConnection(port);
|
await this.establishConnection(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this.client?.close();
|
||||||
|
}
|
||||||
|
|
||||||
getDetectedIdeDisplayName(): string | undefined {
|
getDetectedIdeDisplayName(): string | undefined {
|
||||||
return this.currentIdeDisplayName;
|
return this.currentIdeDisplayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDisconnected() {
|
||||||
|
this.setState(IDEConnectionStatus.Disconnected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue