diff options
| author | christine betts <[email protected]> | 2025-08-14 20:12:57 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-08-14 20:12:57 +0000 |
| commit | 5c5fc89eb16afb65a5bbcb30e3bc576ed55e66b8 (patch) | |
| tree | bf83a82c461085cf06d1082b251eadbbd6091ae1 /packages/core/src/ide | |
| parent | e06d774996c0f09e1881201dca278720af2bf5b5 (diff) | |
[ide-mode] Support multi-folder workspaces (#6177)
Diffstat (limited to 'packages/core/src/ide')
| -rw-r--r-- | packages/core/src/ide/ide-client.test.ts | 68 | ||||
| -rw-r--r-- | packages/core/src/ide/ide-client.ts | 64 |
2 files changed, 106 insertions, 26 deletions
diff --git a/packages/core/src/ide/ide-client.test.ts b/packages/core/src/ide/ide-client.test.ts new file mode 100644 index 00000000..6955e495 --- /dev/null +++ b/packages/core/src/ide/ide-client.test.ts @@ -0,0 +1,68 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect } from 'vitest'; +import { IdeClient } from './ide-client.js'; + +describe('IdeClient.validateWorkspacePath', () => { + it('should return valid if cwd is a subpath of the IDE workspace path', () => { + const result = IdeClient.validateWorkspacePath( + '/Users/person/gemini-cli', + 'VS Code', + '/Users/person/gemini-cli/sub-dir', + ); + expect(result.isValid).toBe(true); + }); + + it('should return invalid if GEMINI_CLI_IDE_WORKSPACE_PATH is undefined', () => { + const result = IdeClient.validateWorkspacePath( + undefined, + 'VS Code', + '/Users/person/gemini-cli/sub-dir', + ); + expect(result.isValid).toBe(false); + expect(result.error).toContain('Failed to connect'); + }); + + it('should return invalid if GEMINI_CLI_IDE_WORKSPACE_PATH is empty', () => { + const result = IdeClient.validateWorkspacePath( + '', + 'VS Code', + '/Users/person/gemini-cli/sub-dir', + ); + expect(result.isValid).toBe(false); + expect(result.error).toContain('please open a workspace folder'); + }); + + it('should return invalid if cwd is not within the IDE workspace path', () => { + const result = IdeClient.validateWorkspacePath( + '/some/other/path', + 'VS Code', + '/Users/person/gemini-cli/sub-dir', + ); + expect(result.isValid).toBe(false); + expect(result.error).toContain('Directory mismatch'); + }); + + it('should handle multiple workspace paths and return valid', () => { + const result = IdeClient.validateWorkspacePath( + '/some/other/path:/Users/person/gemini-cli', + 'VS Code', + '/Users/person/gemini-cli/sub-dir', + ); + expect(result.isValid).toBe(true); + }); + + it('should return invalid if cwd is not in any of the multiple workspace paths', () => { + const result = IdeClient.validateWorkspacePath( + '/some/other/path:/another/path', + 'VS Code', + '/Users/person/gemini-cli/sub-dir', + ); + expect(result.isValid).toBe(false); + expect(result.error).toContain('Directory mismatch'); + }); +}); diff --git a/packages/core/src/ide/ide-client.ts b/packages/core/src/ide/ide-client.ts index 94107f21..810e82e0 100644 --- a/packages/core/src/ide/ide-client.ts +++ b/packages/core/src/ide/ide-client.ts @@ -5,6 +5,7 @@ */ import * as fs from 'node:fs'; +import { isSubpath } from '../utils/paths.js'; import { detectIde, DetectedIde, getIdeInfo } from '../ide/detect-ide.js'; import { ideContext, @@ -93,7 +94,14 @@ export class IdeClient { this.setState(IDEConnectionStatus.Connecting); - if (!this.validateWorkspacePath()) { + const { isValid, error } = IdeClient.validateWorkspacePath( + process.env['GEMINI_CLI_IDE_WORKSPACE_PATH'], + this.currentIdeDisplayName, + process.cwd(), + ); + + if (!isValid) { + this.setState(IDEConnectionStatus.Disconnected, error, true); return; } @@ -245,37 +253,41 @@ export class IdeClient { } } - private validateWorkspacePath(): boolean { - const ideWorkspacePath = process.env['GEMINI_CLI_IDE_WORKSPACE_PATH']; + static validateWorkspacePath( + ideWorkspacePath: string | undefined, + currentIdeDisplayName: string | undefined, + cwd: string, + ): { isValid: boolean; error?: string } { if (ideWorkspacePath === undefined) { - this.setState( - IDEConnectionStatus.Disconnected, - `Failed to connect to IDE companion extension for ${this.currentIdeDisplayName}. Please ensure the extension is running and try refreshing your terminal. To install the extension, run /ide install.`, - true, - ); - return false; + return { + isValid: false, + error: `Failed to connect to IDE companion extension for ${currentIdeDisplayName}. Please ensure the extension is running and try refreshing your terminal. To install the extension, run /ide install.`, + }; } + if (ideWorkspacePath === '') { - this.setState( - IDEConnectionStatus.Disconnected, - `To use this feature, please open a single workspace folder in ${this.currentIdeDisplayName} and try again.`, - true, - ); - return false; + return { + isValid: false, + error: `To use this feature, please open a workspace folder in ${currentIdeDisplayName} and try again.`, + }; } - const idePath = getRealPath(ideWorkspacePath).toLocaleLowerCase(); - const cwd = getRealPath(process.cwd()).toLocaleLowerCase(); - const rel = path.relative(idePath, cwd); - if (rel.startsWith('..') || path.isAbsolute(rel)) { - this.setState( - IDEConnectionStatus.Disconnected, - `Directory mismatch. Gemini CLI is running in a different location than the open workspace in ${this.currentIdeDisplayName}. Please run the CLI from the same directory as your project's root folder.`, - true, - ); - return false; + const ideWorkspacePaths = ideWorkspacePath.split(':'); + const realCwd = getRealPath(cwd); + const isWithinWorkspace = ideWorkspacePaths.some((workspacePath) => { + const idePath = getRealPath(workspacePath); + return isSubpath(idePath, realCwd); + }); + + if (!isWithinWorkspace) { + return { + isValid: false, + error: `Directory mismatch. Gemini CLI is running in a different location than the open workspace in ${currentIdeDisplayName}. Please run the CLI from one of the following directories: ${ideWorkspacePaths.join( + ', ', + )}`, + }; } - return true; + return { isValid: true }; } private getPortFromEnv(): string | undefined { |
