Add hint to enable IDE integration for users running in VS Code (#5610)

This commit is contained in:
Shreya Keshive
2025-08-06 15:47:58 -04:00
committed by GitHub
parent 1fb680bacc
commit 024b8207eb
7 changed files with 244 additions and 5 deletions

View File

@@ -16,6 +16,7 @@ import {
SandboxConfig,
GeminiClient,
ideContext,
type AuthType,
} from '@google/gemini-cli-core';
import { LoadedSettings, SettingsFile, Settings } from '../config/settings.js';
import process from 'node:process';
@@ -84,6 +85,7 @@ interface MockServerConfig {
getAllGeminiMdFilenames: Mock<() => string[]>;
getGeminiClient: Mock<() => GeminiClient | undefined>;
getUserTier: Mock<() => Promise<string | undefined>>;
getIdeClient: Mock<() => { getCurrentIde: Mock<() => string | undefined> }>;
}
// Mock @google/gemini-cli-core and its Config class
@@ -157,6 +159,9 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
getWorkspaceContext: vi.fn(() => ({
getDirectories: vi.fn(() => []),
})),
getIdeClient: vi.fn(() => ({
getCurrentIde: vi.fn(() => 'vscode'),
})),
};
});
@@ -182,6 +187,7 @@ vi.mock('./hooks/useGeminiStream', () => ({
submitQuery: vi.fn(),
initError: null,
pendingHistoryItems: [],
thought: null,
})),
}));
@@ -233,7 +239,7 @@ vi.mock('./utils/updateCheck.js', () => ({
checkForUpdates: vi.fn(),
}));
vi.mock('./config/auth.js', () => ({
vi.mock('../config/auth.js', () => ({
validateAuthMethod: vi.fn(),
}));

View File

@@ -39,7 +39,7 @@ import { EditorSettingsDialog } from './components/EditorSettingsDialog.js';
import { ShellConfirmationDialog } from './components/ShellConfirmationDialog.js';
import { Colors } from './colors.js';
import { loadHierarchicalGeminiMemory } from '../config/config.js';
import { LoadedSettings } from '../config/settings.js';
import { LoadedSettings, SettingScope } from '../config/settings.js';
import { Tips } from './components/Tips.js';
import { ConsolePatcher } from './utils/ConsolePatcher.js';
import { registerCleanup } from '../utils/cleanup.js';
@@ -62,6 +62,10 @@ import {
type IdeContext,
ideContext,
} from '@google/gemini-cli-core';
import {
IdeIntegrationNudge,
IdeIntegrationNudgeResult,
} from './IdeIntegrationNudge.js';
import { validateAuthMethod } from '../config/auth.js';
import { useLogger } from './hooks/useLogger.js';
import { StreamingContext } from './contexts/StreamingContext.js';
@@ -115,6 +119,15 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
const nightly = version.includes('nightly');
const { history, addItem, clearItems, loadHistory } = useHistory();
const [idePromptAnswered, setIdePromptAnswered] = useState(false);
const currentIDE = config.getIdeClient().getCurrentIde();
const shouldShowIdePrompt =
config.getIdeModeFeature() &&
currentIDE &&
!config.getIdeMode() &&
!settings.merged.hasSeenIdeIntegrationNudge &&
!idePromptAnswered;
useEffect(() => {
const cleanup = setUpdateHandler(addItem, setUpdateInfo);
return cleanup;
@@ -538,6 +551,27 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
[submitQuery],
);
const handleIdePromptComplete = useCallback(
(result: IdeIntegrationNudgeResult) => {
if (result === 'yes') {
handleSlashCommand('/ide install');
settings.setValue(
SettingScope.User,
'hasSeenIdeIntegrationNudge',
true,
);
} else if (result === 'dismiss') {
settings.setValue(
SettingScope.User,
'hasSeenIdeIntegrationNudge',
true,
);
}
setIdePromptAnswered(true);
},
[handleSlashCommand, settings],
);
const { handleInput: vimHandleInput } = useVim(buffer, handleFinalSubmit);
const pendingHistoryItems = [...pendingSlashCommandHistoryItems];
pendingHistoryItems.push(...pendingGeminiHistoryItems);
@@ -768,6 +802,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
</Box>
);
}
const mainAreaWidth = Math.floor(terminalWidth * 0.9);
const debugConsoleMaxHeight = Math.floor(Math.max(terminalHeight * 0.2, 5));
// Arbitrary threshold to ensure that items in the static area are large
@@ -859,7 +894,13 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
</Box>
)}
{shellConfirmationRequest ? (
{shouldShowIdePrompt ? (
<IdeIntegrationNudge
question="Do you want to connect your VS Code editor to Gemini CLI?"
description="If you select Yes, we'll install an extension that allows the CLI to access your open files and display diffs directly in VS Code."
onComplete={handleIdePromptComplete}
/>
) : shellConfirmationRequest ? (
<ShellConfirmationDialog request={shellConfirmationRequest} />
) : isThemeDialogOpen ? (
<Box flexDirection="column">

View File

@@ -0,0 +1,70 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { Box, Text, useInput } from 'ink';
import {
RadioButtonSelect,
RadioSelectItem,
} from './components/shared/RadioButtonSelect.js';
export type IdeIntegrationNudgeResult = 'yes' | 'no' | 'dismiss';
interface IdeIntegrationNudgeProps {
question: string;
description?: string;
onComplete: (result: IdeIntegrationNudgeResult) => void;
}
export function IdeIntegrationNudge({
question,
description,
onComplete,
}: IdeIntegrationNudgeProps) {
useInput((_input, key) => {
if (key.escape) {
onComplete('no');
}
});
const OPTIONS: Array<RadioSelectItem<IdeIntegrationNudgeResult>> = [
{
label: 'Yes',
value: 'yes',
},
{
label: 'No (esc)',
value: 'no',
},
{
label: "No, don't ask again",
value: 'dismiss',
},
];
return (
<Box
flexDirection="column"
borderStyle="round"
borderColor="yellow"
padding={1}
width="100%"
marginLeft={1}
>
<Box marginBottom={1} flexDirection="column">
<Text>
<Text color="yellow">{'> '}</Text>
{question}
</Text>
{description && <Text dimColor>{description}</Text>}
</Box>
<RadioButtonSelect
items={OPTIONS}
onSelect={onComplete}
isFocused={true}
/>
</Box>
);
}