From 325bb8913776c60b763ee5f66375a4ca90d22ce0 Mon Sep 17 00:00:00 2001 From: christine betts Date: Wed, 30 Jul 2025 22:36:24 +0000 Subject: [PATCH] Add toggleable IDE mode setting (#5146) --- packages/cli/src/config/config.test.ts | 10 +- packages/cli/src/config/config.ts | 14 +- packages/cli/src/config/settings.ts | 4 +- packages/cli/src/ui/App.test.tsx | 1 + packages/cli/src/ui/App.tsx | 7 +- .../cli/src/ui/commands/ideCommand.test.ts | 52 +++-- packages/cli/src/ui/commands/ideCommand.ts | 191 +++++++++++------- .../ui/hooks/slashCommandProcessor.test.ts | 1 + .../cli/src/ui/hooks/slashCommandProcessor.ts | 4 +- packages/core/src/config/config.ts | 29 ++- packages/core/src/core/client.test.ts | 19 +- packages/core/src/core/client.ts | 2 +- packages/core/src/ide/ide-client.ts | 20 +- 13 files changed, 231 insertions(+), 123 deletions(-) diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index 1dd09f4b..d87d0c8f 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -916,7 +916,7 @@ describe('loadCliConfig extensions', () => { }); }); -describe('loadCliConfig ideMode', () => { +describe('loadCliConfig ideModeFeature', () => { const originalArgv = process.argv; const originalEnv = { ...process.env }; @@ -939,16 +939,16 @@ describe('loadCliConfig ideMode', () => { const settings: Settings = {}; const argv = await parseArguments(); 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']; const argv = await parseArguments(); process.env.TERM_PROGRAM = 'vscode'; process.env.SANDBOX = 'true'; - const settings: Settings = { ideMode: true }; + const settings: Settings = { ideModeFeature: true }; const config = await loadCliConfig(settings, [], 'test-session', argv); - expect(config.getIdeMode()).toBe(false); + expect(config.getIdeModeFeature()).toBe(false); }); }); diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 1cc78888..d650a9af 100644 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -59,7 +59,7 @@ export interface CliArgs { experimentalAcp: boolean | undefined; extensions: string[] | undefined; listExtensions: boolean | undefined; - ideMode: boolean | undefined; + ideModeFeature: boolean | undefined; proxy: string | undefined; includeDirectories: string[] | undefined; } @@ -191,7 +191,7 @@ export async function parseArguments(): Promise { type: 'boolean', description: 'List all available extensions and exit.', }) - .option('ide-mode', { + .option('ide-mode-feature', { type: 'boolean', description: 'Run in IDE mode?', }) @@ -268,10 +268,13 @@ export async function loadCliConfig( (v) => v === 'true' || v === '1', ); - const ideMode = - (argv.ideMode ?? settings.ideMode ?? false) && !process.env.SANDBOX; + const ideMode = settings.ideMode ?? false; - const ideClient = IdeClient.getInstance(ideMode); + const ideModeFeature = + (argv.ideModeFeature ?? settings.ideModeFeature ?? false) && + !process.env.SANDBOX; + + const ideClient = IdeClient.getInstance(ideMode && ideModeFeature); const allExtensions = annotateActiveExtensions( extensions, @@ -429,6 +432,7 @@ export async function loadCliConfig( noBrowser: !!process.env.NO_BROWSER, summarizeToolOutput: settings.summarizeToolOutput, ideMode, + ideModeFeature, ideClient, }); } diff --git a/packages/cli/src/config/settings.ts b/packages/cli/src/config/settings.ts index 17c1d0d5..5d1b1aaf 100644 --- a/packages/cli/src/config/settings.ts +++ b/packages/cli/src/config/settings.ts @@ -99,7 +99,9 @@ export interface Settings { vimMode?: boolean; - // Add other settings here. + // Flag to be removed post-launch. + ideModeFeature?: boolean; + /// IDE mode setting configured via slash command toggle. ideMode?: boolean; // Setting for disabling auto-update. diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx index 13ddb77d..79b9ce86 100644 --- a/packages/cli/src/ui/App.test.tsx +++ b/packages/cli/src/ui/App.test.tsx @@ -151,6 +151,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => { setFlashFallbackHandler: vi.fn(), getSessionId: vi.fn(() => 'test-session-id'), getUserTier: vi.fn().mockResolvedValue(undefined), + getIdeModeFeature: vi.fn(() => false), getIdeMode: vi.fn(() => false), getWorkspaceContext: vi.fn(() => ({ getDirectories: vi.fn(() => []), diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index 2e899cc1..db9e5be4 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -573,7 +573,12 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { if (Object.keys(mcpServers || {}).length > 0) { 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); } else if (key.ctrl && (input === 'c' || input === 'C')) { handleExit(ctrlCPressedOnce, setCtrlCPressedOnce, ctrlCTimerRef); diff --git a/packages/cli/src/ui/commands/ideCommand.test.ts b/packages/cli/src/ui/commands/ideCommand.test.ts index 3c73f52c..3c73549c 100644 --- a/packages/cli/src/ui/commands/ideCommand.test.ts +++ b/packages/cli/src/ui/commands/ideCommand.test.ts @@ -32,11 +32,19 @@ describe('ideCommand', () => { ui: { addItem: vi.fn(), }, + services: { + settings: { + setValue: vi.fn(), + }, + }, } as unknown as CommandContext; mockConfig = { + getIdeModeFeature: vi.fn(), getIdeMode: vi.fn(), getIdeClient: vi.fn(), + setIdeMode: vi.fn(), + setIdeClientDisconnected: vi.fn(), } as unknown as Config; platformSpy = vi.spyOn(process, 'platform', 'get'); @@ -46,13 +54,14 @@ describe('ideCommand', () => { vi.restoreAllMocks(); }); - it('should return null if ideMode is not enabled', () => { - vi.mocked(mockConfig.getIdeMode).mockReturnValue(false); + it('should return null if ideModeFeature is not enabled', () => { + vi.mocked(mockConfig.getIdeModeFeature).mockReturnValue(false); const command = ideCommand(mockConfig); 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.getIdeClient).mockReturnValue({ getCurrentIde: () => DetectedIde.VSCode, @@ -60,19 +69,20 @@ describe('ideCommand', () => { const command = ideCommand(mockConfig); expect(command).not.toBeNull(); expect(command?.name).toBe('ide'); - expect(command?.subCommands).toHaveLength(2); - expect(command?.subCommands?.[0].name).toBe('status'); - expect(command?.subCommands?.[1].name).toBe('install'); + expect(command?.subCommands).toHaveLength(3); + expect(command?.subCommands?.[0].name).toBe('disable'); + expect(command?.subCommands?.[1].name).toBe('status'); + expect(command?.subCommands?.[2].name).toBe('install'); }); describe('status subcommand', () => { const mockGetConnectionStatus = vi.fn(); beforeEach(() => { - vi.mocked(mockConfig.getIdeMode).mockReturnValue(true); + vi.mocked(mockConfig.getIdeModeFeature).mockReturnValue(true); vi.mocked(mockConfig.getIdeClient).mockReturnValue({ getConnectionStatus: mockGetConnectionStatus, getCurrentIde: () => DetectedIde.VSCode, - } as ReturnType); + } as unknown as ReturnType); }); it('should show connected status', () => { @@ -80,7 +90,8 @@ describe('ideCommand', () => { status: core.IDEConnectionStatus.Connected, }); 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(result).toEqual({ type: 'message', @@ -94,7 +105,8 @@ describe('ideCommand', () => { status: core.IDEConnectionStatus.Connecting, }); 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(result).toEqual({ type: 'message', @@ -107,7 +119,8 @@ describe('ideCommand', () => { status: core.IDEConnectionStatus.Disconnected, }); 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(result).toEqual({ type: 'message', @@ -123,7 +136,8 @@ describe('ideCommand', () => { details, }); 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(result).toEqual({ type: 'message', @@ -136,10 +150,12 @@ describe('ideCommand', () => { describe('install subcommand', () => { const mockInstall = vi.fn(); beforeEach(() => { + vi.mocked(mockConfig.getIdeModeFeature).mockReturnValue(true); vi.mocked(mockConfig.getIdeMode).mockReturnValue(true); vi.mocked(mockConfig.getIdeClient).mockReturnValue({ getCurrentIde: () => DetectedIde.VSCode, - } as ReturnType); + getConnectionStatus: vi.fn(), + } as unknown as ReturnType); vi.mocked(core.getIdeInstaller).mockReturnValue({ install: mockInstall, isInstalled: vi.fn(), @@ -154,7 +170,10 @@ describe('ideCommand', () => { }); 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(mockInstall).toHaveBeenCalled(); @@ -181,7 +200,10 @@ describe('ideCommand', () => { }); 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(mockInstall).toHaveBeenCalled(); diff --git a/packages/cli/src/ui/commands/ideCommand.ts b/packages/cli/src/ui/commands/ideCommand.ts index 26b0f57d..1da7d6b0 100644 --- a/packages/cli/src/ui/commands/ideCommand.ts +++ b/packages/cli/src/ui/commands/ideCommand.ts @@ -6,9 +6,9 @@ import { Config, + IDEConnectionStatus, getIdeDisplayName, getIdeInstaller, - IDEConnectionStatus, } from '@google/gemini-cli-core'; import { CommandContext, @@ -16,91 +16,130 @@ import { SlashCommandActionReturn, CommandKind, } from './types.js'; +import { SettingScope } from '../../config/settings.js'; export const ideCommand = (config: Config | null): SlashCommand | null => { - if (!config?.getIdeMode()) { + if (!config?.getIdeModeFeature()) { return null; } const currentIDE = config.getIdeClient().getCurrentIde(); if (!currentIDE) { - throw new Error( - 'IDE slash command should not be available if not running in an IDE', - ); + return null; } - return { + const ideSlashCommand: SlashCommand = { name: 'ide', description: 'manage IDE integration', kind: CommandKind.BUILT_IN, - subCommands: [ - { - name: 'status', - description: 'check status of IDE integration', - kind: CommandKind.BUILT_IN, - action: (_context: CommandContext): SlashCommandActionReturn => { - const connection = config.getIdeClient().getConnectionStatus(); - switch (connection?.status) { - case IDEConnectionStatus.Connected: - return { - type: 'message', - messageType: 'info', - content: `🟢 Connected`, - } as const; - case IDEConnectionStatus.Connecting: - return { - type: 'message', - messageType: 'info', - content: `🟡 Connecting...`, - } as const; - default: { - let content = `🔴 Disconnected`; - if (connection?.details) { - content += `: ${connection.details}`; - } - return { - type: 'message', - messageType: 'error', - content, - } as const; - } - } - }, - }, - { - name: 'install', - description: `install required IDE companion ${getIdeDisplayName(currentIDE)} extension `, - kind: CommandKind.BUILT_IN, - action: async (context) => { - const installer = getIdeInstaller(currentIDE); - if (!installer) { - context.ui.addItem( - { - type: 'error', - text: 'No installer available for your configured IDE.', - }, - Date.now(), - ); - return; - } - - context.ui.addItem( - { - type: 'info', - text: `Installing IDE companion extension...`, - }, - Date.now(), - ); - - const result = await installer.install(); - context.ui.addItem( - { - type: result.success ? 'info' : 'error', - text: result.message, - }, - Date.now(), - ); - }, - }, - ], + subCommands: [], }; + + const statusCommand: SlashCommand = { + name: 'status', + description: 'check status of IDE integration', + kind: CommandKind.BUILT_IN, + action: (_context: CommandContext): SlashCommandActionReturn => { + const connection = config.getIdeClient().getConnectionStatus(); + switch (connection?.status) { + case IDEConnectionStatus.Connected: + return { + type: 'message', + messageType: 'info', + content: `🟢 Connected`, + } as const; + case IDEConnectionStatus.Connecting: + return { + type: 'message', + messageType: 'info', + content: `🟡 Connecting...`, + } as const; + default: { + let content = `🔴 Disconnected`; + if (connection?.details) { + content += `: ${connection.details}`; + } + return { + type: 'message', + messageType: 'error', + content, + } as const; + } + } + }, + }; + + const installCommand: SlashCommand = { + name: 'install', + description: `install required IDE companion ${getIdeDisplayName(currentIDE)} extension `, + kind: CommandKind.BUILT_IN, + action: async (context) => { + const installer = getIdeInstaller(currentIDE); + if (!installer) { + context.ui.addItem( + { + type: 'error', + text: 'No installer available for your configured IDE.', + }, + Date.now(), + ); + return; + } + + context.ui.addItem( + { + type: 'info', + text: `Installing IDE companion extension...`, + }, + Date.now(), + ); + + const result = await installer.install(); + context.ui.addItem( + { + type: result.success ? 'info' : 'error', + text: result.message, + }, + 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; }; diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts index 2dc206d7..d9fe8530 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts @@ -101,6 +101,7 @@ describe('useSlashCommandProcessor', () => { setHistory: vi.fn().mockResolvedValue(undefined), })), getExtensions: vi.fn(() => []), + getIdeMode: vi.fn(() => false), } as unknown as Config; const mockSettings = {} as LoadedSettings; diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.ts index e315ba97..a2a1837d 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.ts @@ -185,6 +185,8 @@ export const useSlashCommandProcessor = ( ], ); + const ideMode = config?.getIdeMode(); + useEffect(() => { const controller = new AbortController(); const load = async () => { @@ -205,7 +207,7 @@ export const useSlashCommandProcessor = ( return () => { controller.abort(); }; - }, [config]); + }, [config, ideMode]); const handleSlashCommand = useCallback( async ( diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index e62e2962..edb24351 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -184,6 +184,7 @@ export interface ConfigParameters { blockedMcpServers?: Array<{ name: string; extensionName: string }>; noBrowser?: boolean; summarizeToolOutput?: Record; + ideModeFeature?: boolean; ideMode?: boolean; ideClient: IdeClient; } @@ -228,8 +229,9 @@ export class Config { private readonly model: string; private readonly extensionContextFilePaths: string[]; private readonly noBrowser: boolean; - private readonly ideMode: boolean; - private readonly ideClient: IdeClient; + private readonly ideModeFeature: boolean; + private ideMode: boolean; + private ideClient: IdeClient; private inFallbackMode = false; private readonly maxSessionTurns: number; private readonly listExtensions: boolean; @@ -298,7 +300,8 @@ export class Config { this._blockedMcpServers = params.blockedMcpServers ?? []; this.noBrowser = params.noBrowser ?? false; this.summarizeToolOutput = params.summarizeToolOutput; - this.ideMode = params.ideMode ?? false; + this.ideModeFeature = params.ideModeFeature ?? false; + this.ideMode = params.ideMode ?? true; this.ideClient = params.ideClient; if (params.contextFileName) { @@ -589,14 +592,30 @@ export class Config { return this.summarizeToolOutput; } - getIdeMode(): boolean { - return this.ideMode; + getIdeModeFeature(): boolean { + return this.ideModeFeature; } getIdeClient(): 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 { if (!this.gitService) { this.gitService = new GitService(this.targetDir); diff --git a/packages/core/src/core/client.test.ts b/packages/core/src/core/client.test.ts index 96ddec1c..68d8c231 100644 --- a/packages/core/src/core/client.test.ts +++ b/packages/core/src/core/client.test.ts @@ -199,7 +199,8 @@ describe('Gemini Client (client.ts)', () => { setQuotaErrorOccurred: vi.fn(), getNoBrowser: vi.fn().mockReturnValue(false), getUsageStatisticsEnabled: vi.fn().mockReturnValue(true), - getIdeMode: vi.fn().mockReturnValue(false), + getIdeModeFeature: vi.fn().mockReturnValue(false), + getIdeMode: vi.fn().mockReturnValue(true), getWorkspaceContext: vi.fn().mockReturnValue({ getDirectories: vi.fn().mockReturnValue(['/test/dir']), }), @@ -649,7 +650,7 @@ describe('Gemini Client (client.ts)', () => { }); describe('sendMessageStream', () => { - it('should include IDE context when ideMode is enabled', async () => { + it('should include IDE context when ideModeFeature is enabled', async () => { // Arrange vi.mocked(ideContext.getIdeContext).mockReturnValue({ 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* () { 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 vi.mocked(ideContext.getIdeContext).mockReturnValue({ 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* () { 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 vi.mocked(ideContext.getIdeContext).mockReturnValue({ 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* () { 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 vi.mocked(ideContext.getIdeContext).mockReturnValue({ 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* () { yield { type: 'content', value: 'Hello' }; diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index ecc7c242..49a30294 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -339,7 +339,7 @@ export class GeminiClient { yield { type: GeminiEventType.ChatCompressed, value: compressed }; } - if (this.config.getIdeMode()) { + if (this.config.getIdeModeFeature() && this.config.getIdeMode()) { const ideContextState = ideContext.getIdeContext(); const openFiles = ideContextState?.workspaceState?.openFiles; diff --git a/packages/core/src/ide/ide-client.ts b/packages/core/src/ide/ide-client.ts index 4dd720dd..be24db3e 100644 --- a/packages/core/src/ide/ide-client.ts +++ b/packages/core/src/ide/ide-client.ts @@ -41,14 +41,14 @@ export class IdeClient { private readonly currentIde: DetectedIde | undefined; private readonly currentIdeDisplayName: string | undefined; - private constructor(ideMode: boolean) { - if (!ideMode) { - return; - } + constructor(ideMode: boolean) { this.currentIde = detectIde(); if (this.currentIde) { this.currentIdeDisplayName = getIdeDisplayName(this.currentIde); } + if (!ideMode) { + return; + } this.init().catch((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) { let transport: StreamableHTTPClientTransport | undefined; try { @@ -189,7 +193,15 @@ export class IdeClient { await this.establishConnection(port); } + dispose() { + this.client?.close(); + } + getDetectedIdeDisplayName(): string | undefined { return this.currentIdeDisplayName; } + + setDisconnected() { + this.setState(IDEConnectionStatus.Disconnected); + } }