summaryrefslogtreecommitdiff
path: root/packages/cli
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli')
-rw-r--r--packages/cli/src/ui/App.test.tsx44
-rw-r--r--packages/cli/src/ui/App.tsx10
-rw-r--r--packages/cli/src/ui/components/ContextSummaryDisplay.tsx22
-rw-r--r--packages/cli/src/ui/components/IDEContextDetailDisplay.tsx52
4 files changed, 107 insertions, 21 deletions
diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx
index 0712d810..56093562 100644
--- a/packages/cli/src/ui/App.test.tsx
+++ b/packages/cli/src/ui/App.test.tsx
@@ -281,6 +281,7 @@ describe('App UI', () => {
it('should display active file when available', async () => {
vi.mocked(ideContext.getOpenFilesContext).mockReturnValue({
activeFile: '/path/to/my-file.ts',
+ recentOpenFiles: [{ filePath: '/path/to/my-file.ts', content: 'hello' }],
selectedText: 'hello',
});
@@ -293,7 +294,7 @@ describe('App UI', () => {
);
currentUnmount = unmount;
await Promise.resolve();
- expect(lastFrame()).toContain('Open File (my-file.ts)');
+ expect(lastFrame()).toContain('1 recent file (ctrl+e to view)');
});
it('should not display active file when not available', async () => {
@@ -316,9 +317,11 @@ describe('App UI', () => {
it('should display active file and other context', async () => {
vi.mocked(ideContext.getOpenFilesContext).mockReturnValue({
activeFile: '/path/to/my-file.ts',
+ recentOpenFiles: [{ filePath: '/path/to/my-file.ts', content: 'hello' }],
selectedText: 'hello',
});
mockConfig.getGeminiMdFileCount.mockReturnValue(1);
+ mockConfig.getAllGeminiMdFilenames.mockReturnValue(['GEMINI.md']);
const { lastFrame, unmount } = render(
<App
@@ -329,11 +332,14 @@ describe('App UI', () => {
);
currentUnmount = unmount;
await Promise.resolve();
- expect(lastFrame()).toContain('Open File (my-file.ts) | 1 GEMINI.md File');
+ expect(lastFrame()).toContain(
+ 'Using: 1 recent file (ctrl+e to view) | 1 GEMINI.md file',
+ );
});
it('should display default "GEMINI.md" in footer when contextFileName is not set and count is 1', async () => {
mockConfig.getGeminiMdFileCount.mockReturnValue(1);
+ mockConfig.getAllGeminiMdFilenames.mockReturnValue(['GEMINI.md']);
// For this test, ensure showMemoryUsage is false or debugMode is false if it relies on that
mockConfig.getDebugMode.mockReturnValue(false);
mockConfig.getShowMemoryUsage.mockReturnValue(false);
@@ -347,11 +353,15 @@ describe('App UI', () => {
);
currentUnmount = unmount;
await Promise.resolve(); // Wait for any async updates
- expect(lastFrame()).toContain('Using: 1 GEMINI.md File');
+ expect(lastFrame()).toContain('Using: 1 GEMINI.md file');
});
it('should display default "GEMINI.md" with plural when contextFileName is not set and count is > 1', async () => {
mockConfig.getGeminiMdFileCount.mockReturnValue(2);
+ mockConfig.getAllGeminiMdFilenames.mockReturnValue([
+ 'GEMINI.md',
+ 'GEMINI.md',
+ ]);
mockConfig.getDebugMode.mockReturnValue(false);
mockConfig.getShowMemoryUsage.mockReturnValue(false);
@@ -364,7 +374,7 @@ describe('App UI', () => {
);
currentUnmount = unmount;
await Promise.resolve();
- expect(lastFrame()).toContain('Using: 2 GEMINI.md Files');
+ expect(lastFrame()).toContain('Using: 2 GEMINI.md files');
});
it('should display custom contextFileName in footer when set and count is 1', async () => {
@@ -372,6 +382,7 @@ describe('App UI', () => {
workspace: { contextFileName: 'AGENTS.md', theme: 'Default' },
});
mockConfig.getGeminiMdFileCount.mockReturnValue(1);
+ mockConfig.getAllGeminiMdFilenames.mockReturnValue(['AGENTS.md']);
mockConfig.getDebugMode.mockReturnValue(false);
mockConfig.getShowMemoryUsage.mockReturnValue(false);
@@ -384,7 +395,7 @@ describe('App UI', () => {
);
currentUnmount = unmount;
await Promise.resolve();
- expect(lastFrame()).toContain('Using: 1 AGENTS.md File');
+ expect(lastFrame()).toContain('Using: 1 AGENTS.md file');
});
it('should display a generic message when multiple context files with different names are provided', async () => {
@@ -395,6 +406,10 @@ describe('App UI', () => {
},
});
mockConfig.getGeminiMdFileCount.mockReturnValue(2);
+ mockConfig.getAllGeminiMdFilenames.mockReturnValue([
+ 'AGENTS.md',
+ 'CONTEXT.md',
+ ]);
mockConfig.getDebugMode.mockReturnValue(false);
mockConfig.getShowMemoryUsage.mockReturnValue(false);
@@ -407,7 +422,7 @@ describe('App UI', () => {
);
currentUnmount = unmount;
await Promise.resolve();
- expect(lastFrame()).toContain('Using: 2 Context Files');
+ expect(lastFrame()).toContain('Using: 2 context files');
});
it('should display custom contextFileName with plural when set and count is > 1', async () => {
@@ -415,6 +430,11 @@ describe('App UI', () => {
workspace: { contextFileName: 'MY_NOTES.TXT', theme: 'Default' },
});
mockConfig.getGeminiMdFileCount.mockReturnValue(3);
+ mockConfig.getAllGeminiMdFilenames.mockReturnValue([
+ 'MY_NOTES.TXT',
+ 'MY_NOTES.TXT',
+ 'MY_NOTES.TXT',
+ ]);
mockConfig.getDebugMode.mockReturnValue(false);
mockConfig.getShowMemoryUsage.mockReturnValue(false);
@@ -427,7 +447,7 @@ describe('App UI', () => {
);
currentUnmount = unmount;
await Promise.resolve();
- expect(lastFrame()).toContain('Using: 3 MY_NOTES.TXT Files');
+ expect(lastFrame()).toContain('Using: 3 MY_NOTES.TXT files');
});
it('should not display context file message if count is 0, even if contextFileName is set', async () => {
@@ -435,6 +455,7 @@ describe('App UI', () => {
workspace: { contextFileName: 'ANY_FILE.MD', theme: 'Default' },
});
mockConfig.getGeminiMdFileCount.mockReturnValue(0);
+ mockConfig.getAllGeminiMdFilenames.mockReturnValue([]);
mockConfig.getDebugMode.mockReturnValue(false);
mockConfig.getShowMemoryUsage.mockReturnValue(false);
@@ -452,6 +473,10 @@ describe('App UI', () => {
it('should display GEMINI.md and MCP server count when both are present', async () => {
mockConfig.getGeminiMdFileCount.mockReturnValue(2);
+ mockConfig.getAllGeminiMdFilenames.mockReturnValue([
+ 'GEMINI.md',
+ 'GEMINI.md',
+ ]);
mockConfig.getMcpServers.mockReturnValue({
server1: {} as MCPServerConfig,
});
@@ -467,11 +492,12 @@ describe('App UI', () => {
);
currentUnmount = unmount;
await Promise.resolve();
- expect(lastFrame()).toContain('1 MCP Server');
+ expect(lastFrame()).toContain('1 MCP server');
});
it('should display only MCP server count when GEMINI.md count is 0', async () => {
mockConfig.getGeminiMdFileCount.mockReturnValue(0);
+ mockConfig.getAllGeminiMdFilenames.mockReturnValue([]);
mockConfig.getMcpServers.mockReturnValue({
server1: {} as MCPServerConfig,
server2: {} as MCPServerConfig,
@@ -488,7 +514,7 @@ describe('App UI', () => {
);
currentUnmount = unmount;
await Promise.resolve();
- expect(lastFrame()).toContain('Using: 2 MCP Servers');
+ expect(lastFrame()).toContain('Using: 2 MCP servers (ctrl+t to view)');
});
it('should display Tips component by default', async () => {
diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx
index 566d6bd5..bd99f01b 100644
--- a/packages/cli/src/ui/App.tsx
+++ b/packages/cli/src/ui/App.tsx
@@ -46,6 +46,7 @@ import { registerCleanup } from '../utils/cleanup.js';
import { DetailedMessagesDisplay } from './components/DetailedMessagesDisplay.js';
import { HistoryItemDisplay } from './components/HistoryItemDisplay.js';
import { ContextSummaryDisplay } from './components/ContextSummaryDisplay.js';
+import { IDEContextDetailDisplay } from './components/IDEContextDetailDisplay.js';
import { useHistory } from './hooks/useHistoryManager.js';
import process from 'node:process';
import {
@@ -148,6 +149,8 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
const [showErrorDetails, setShowErrorDetails] = useState<boolean>(false);
const [showToolDescriptions, setShowToolDescriptions] =
useState<boolean>(false);
+ const [showIDEContextDetail, setShowIDEContextDetail] =
+ useState<boolean>(false);
const [ctrlCPressedOnce, setCtrlCPressedOnce] = useState(false);
const [quittingMessages, setQuittingMessages] = useState<
HistoryItem[] | null
@@ -465,6 +468,8 @@ 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' && ideContext) {
+ setShowIDEContextDetail((prev) => !prev);
} else if (key.ctrl && (input === 'c' || input === 'C')) {
handleExit(ctrlCPressedOnce, setCtrlCPressedOnce, ctrlCTimerRef);
} else if (key.ctrl && (input === 'd' || input === 'D')) {
@@ -861,6 +866,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
}
elapsedTime={elapsedTime}
/>
+
<Box
marginTop={1}
display="flex"
@@ -900,7 +906,9 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
{shellModeActive && <ShellModeIndicator />}
</Box>
</Box>
-
+ {showIDEContextDetail && (
+ <IDEContextDetailDisplay openFiles={openFiles} />
+ )}
{showErrorDetails && (
<OverflowProvider>
<Box flexDirection="column">
diff --git a/packages/cli/src/ui/components/ContextSummaryDisplay.tsx b/packages/cli/src/ui/components/ContextSummaryDisplay.tsx
index 626a2fa5..b166056a 100644
--- a/packages/cli/src/ui/components/ContextSummaryDisplay.tsx
+++ b/packages/cli/src/ui/components/ContextSummaryDisplay.tsx
@@ -8,7 +8,6 @@ import React from 'react';
import { Text } from 'ink';
import { Colors } from '../colors.js';
import { type OpenFiles, type MCPServerConfig } from '@google/gemini-cli-core';
-import path from 'path';
interface ContextSummaryDisplayProps {
geminiMdFileCount: number;
@@ -34,16 +33,17 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
geminiMdFileCount === 0 &&
mcpServerCount === 0 &&
blockedMcpServerCount === 0 &&
- !openFiles?.activeFile
+ (openFiles?.recentOpenFiles?.length ?? 0) === 0
) {
return <Text> </Text>; // Render an empty space to reserve height
}
- const activeFileText = (() => {
- if (!openFiles?.activeFile) {
+ const recentFilesText = (() => {
+ const count = openFiles?.recentOpenFiles?.length ?? 0;
+ if (count === 0) {
return '';
}
- return `Open File (${path.basename(openFiles.activeFile)})`;
+ return `${count} recent file${count > 1 ? 's' : ''} (ctrl+e to view)`;
})();
const geminiMdText = (() => {
@@ -51,8 +51,8 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
return '';
}
const allNamesTheSame = new Set(contextFileNames).size < 2;
- const name = allNamesTheSame ? contextFileNames[0] : 'Context';
- return `${geminiMdFileCount} ${name} File${
+ const name = allNamesTheSame ? contextFileNames[0] : 'context';
+ return `${geminiMdFileCount} ${name} file${
geminiMdFileCount > 1 ? 's' : ''
}`;
})();
@@ -65,14 +65,14 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
const parts = [];
if (mcpServerCount > 0) {
parts.push(
- `${mcpServerCount} MCP Server${mcpServerCount > 1 ? 's' : ''}`,
+ `${mcpServerCount} MCP server${mcpServerCount > 1 ? 's' : ''}`,
);
}
if (blockedMcpServerCount > 0) {
let blockedText = `${blockedMcpServerCount} Blocked`;
if (mcpServerCount === 0) {
- blockedText += ` MCP Server${blockedMcpServerCount > 1 ? 's' : ''}`;
+ blockedText += ` MCP server${blockedMcpServerCount > 1 ? 's' : ''}`;
}
parts.push(blockedText);
}
@@ -81,8 +81,8 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
let summaryText = 'Using: ';
const summaryParts = [];
- if (activeFileText) {
- summaryParts.push(activeFileText);
+ if (recentFilesText) {
+ summaryParts.push(recentFilesText);
}
if (geminiMdText) {
summaryParts.push(geminiMdText);
diff --git a/packages/cli/src/ui/components/IDEContextDetailDisplay.tsx b/packages/cli/src/ui/components/IDEContextDetailDisplay.tsx
new file mode 100644
index 00000000..8d4fb2c9
--- /dev/null
+++ b/packages/cli/src/ui/components/IDEContextDetailDisplay.tsx
@@ -0,0 +1,52 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { Box, Text } from 'ink';
+import { type OpenFiles } from '@google/gemini-cli-core';
+import { Colors } from '../colors.js';
+import path from 'node:path';
+
+interface IDEContextDetailDisplayProps {
+ openFiles: OpenFiles | undefined;
+}
+
+export function IDEContextDetailDisplay({
+ openFiles,
+}: IDEContextDetailDisplayProps) {
+ if (
+ !openFiles ||
+ !openFiles.recentOpenFiles ||
+ openFiles.recentOpenFiles.length === 0
+ ) {
+ return null;
+ }
+ const recentFiles = openFiles.recentOpenFiles || [];
+
+ return (
+ <Box
+ flexDirection="column"
+ marginTop={1}
+ borderStyle="round"
+ borderColor={Colors.AccentCyan}
+ paddingX={1}
+ >
+ <Text color={Colors.AccentCyan} bold>
+ IDE Context (ctrl+e to toggle)
+ </Text>
+ {recentFiles.length > 0 && (
+ <Box flexDirection="column" marginTop={1}>
+ <Text bold>Recent files:</Text>
+ {recentFiles.map((file) => (
+ <Text key={file.filePath}>
+ - {path.basename(file.filePath)}
+ {file.filePath === openFiles.activeFile ? ' (active)' : ''}
+ </Text>
+ ))}
+ </Box>
+ )}
+ </Box>
+ );
+}