diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx
index ed4418e9..e03c80ae 100644
--- a/packages/cli/src/ui/App.test.tsx
+++ b/packages/cli/src/ui/App.test.tsx
@@ -15,6 +15,7 @@ import {
AccessibilitySettings,
SandboxConfig,
GeminiClient,
+ ideContext,
} from '@google/gemini-cli-core';
import { LoadedSettings, SettingsFile, Settings } from '../config/settings.js';
import process from 'node:process';
@@ -146,11 +147,18 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
getIdeMode: vi.fn(() => false),
};
});
+
+ const ideContextMock = {
+ getActiveFileContext: vi.fn(),
+ subscribeToActiveFile: vi.fn(() => vi.fn()), // subscribe returns an unsubscribe function
+ };
+
return {
...actualCore,
Config: ConfigClassMock,
MCPServerConfig: actualCore.MCPServerConfig,
getAllGeminiMdFilenames: vi.fn(() => ['GEMINI.md']),
+ ideContext: ideContextMock,
};
});
@@ -257,6 +265,7 @@ describe('App UI', () => {
// Ensure a theme is set so the theme dialog does not appear.
mockSettings = createMockSettings({ workspace: { theme: 'Default' } });
+ vi.mocked(ideContext.getActiveFileContext).mockReturnValue(undefined);
});
afterEach(() => {
@@ -267,6 +276,64 @@ describe('App UI', () => {
vi.clearAllMocks(); // Clear mocks after each test
});
+ it('should display active file when available', async () => {
+ vi.mocked(ideContext.getActiveFileContext).mockReturnValue({
+ filePath: '/path/to/my-file.ts',
+ content: 'const a = 1;',
+ cursor: 0,
+ });
+
+ const { lastFrame, unmount } = render(
+ ,
+ );
+ currentUnmount = unmount;
+ await Promise.resolve();
+ expect(lastFrame()).toContain('Open File (my-file.ts)');
+ });
+
+ it('should not display active file when not available', async () => {
+ vi.mocked(ideContext.getActiveFileContext).mockReturnValue({
+ filePath: '',
+ content: '',
+ cursor: 0,
+ });
+
+ const { lastFrame, unmount } = render(
+ ,
+ );
+ currentUnmount = unmount;
+ await Promise.resolve();
+ expect(lastFrame()).not.toContain('Open File');
+ });
+
+ it('should display active file and other context', async () => {
+ vi.mocked(ideContext.getActiveFileContext).mockReturnValue({
+ filePath: '/path/to/my-file.ts',
+ content: 'const a = 1;',
+ cursor: 0,
+ });
+ mockConfig.getGeminiMdFileCount.mockReturnValue(1);
+
+ const { lastFrame, unmount } = render(
+ ,
+ );
+ currentUnmount = unmount;
+ await Promise.resolve();
+ expect(lastFrame()).toContain('Open File (my-file.ts) | 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);
// For this test, ensure showMemoryUsage is false or debugMode is false if it relies on that
@@ -282,7 +349,7 @@ 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 () => {
@@ -299,7 +366,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 () => {
@@ -319,7 +386,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 () => {
@@ -342,7 +409,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 () => {
@@ -362,7 +429,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 () => {
@@ -402,7 +469,7 @@ describe('App UI', () => {
);
currentUnmount = unmount;
await Promise.resolve();
- expect(lastFrame()).toContain('server');
+ expect(lastFrame()).toContain('1 MCP Server');
});
it('should display only MCP server count when GEMINI.md count is 0', async () => {
@@ -423,7 +490,7 @@ describe('App UI', () => {
);
currentUnmount = unmount;
await Promise.resolve();
- expect(lastFrame()).toContain('Using 2 MCP servers');
+ expect(lastFrame()).toContain('Using: 2 MCP Servers');
});
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 782e2ff8..39a1f14c 100644
--- a/packages/cli/src/ui/App.tsx
+++ b/packages/cli/src/ui/App.tsx
@@ -58,6 +58,8 @@ import {
FlashFallbackEvent,
logFlashFallback,
AuthType,
+ type ActiveFile,
+ ideContext,
} from '@google/gemini-cli-core';
import { validateAuthMethod } from '../config/auth.js';
import { useLogger } from './hooks/useLogger.js';
@@ -158,6 +160,14 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
const [modelSwitchedFromQuotaError, setModelSwitchedFromQuotaError] =
useState(false);
const [userTier, setUserTier] = useState(undefined);
+ const [activeFile, setActiveFile] = useState();
+
+ useEffect(() => {
+ const unsubscribe = ideContext.subscribeToActiveFile(setActiveFile);
+ // Set the initial value
+ setActiveFile(ideContext.getActiveFileContext());
+ return unsubscribe;
+ }, []);
const openPrivacyNotice = useCallback(() => {
setShowPrivacyNotice(true);
@@ -883,6 +893,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
) : (
;
blockedMcpServers?: Array<{ name: string; extensionName: string }>;
showToolDescriptions?: boolean;
+ activeFile?: ActiveFile;
}
export const ContextSummaryDisplay: React.FC = ({
@@ -23,6 +25,7 @@ export const ContextSummaryDisplay: React.FC = ({
mcpServers,
blockedMcpServers,
showToolDescriptions,
+ activeFile,
}) => {
const mcpServerCount = Object.keys(mcpServers || {}).length;
const blockedMcpServerCount = blockedMcpServers?.length || 0;
@@ -30,18 +33,26 @@ export const ContextSummaryDisplay: React.FC = ({
if (
geminiMdFileCount === 0 &&
mcpServerCount === 0 &&
- blockedMcpServerCount === 0
+ blockedMcpServerCount === 0 &&
+ !activeFile?.filePath
) {
return ; // Render an empty space to reserve height
}
+ const activeFileText = (() => {
+ if (!activeFile?.filePath) {
+ return '';
+ }
+ return `Open File (${path.basename(activeFile.filePath)})`;
+ })();
+
const geminiMdText = (() => {
if (geminiMdFileCount === 0) {
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' : ''
}`;
})();
@@ -54,36 +65,39 @@ export const ContextSummaryDisplay: React.FC = ({
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`;
+ let blockedText = `${blockedMcpServerCount} Blocked`;
if (mcpServerCount === 0) {
- blockedText += ` MCP server${blockedMcpServerCount > 1 ? 's' : ''}`;
+ blockedText += ` MCP Server${blockedMcpServerCount > 1 ? 's' : ''}`;
}
parts.push(blockedText);
}
return parts.join(', ');
})();
- let summaryText = 'Using ';
- if (geminiMdText) {
- summaryText += geminiMdText;
+ let summaryText = 'Using: ';
+ const summaryParts = [];
+ if (activeFileText) {
+ summaryParts.push(activeFileText);
}
- if (geminiMdText && mcpText) {
- summaryText += ' and ';
+ if (geminiMdText) {
+ summaryParts.push(geminiMdText);
}
if (mcpText) {
- summaryText += mcpText;
- // Add ctrl+t hint when MCP servers are available
- if (mcpServers && Object.keys(mcpServers).length > 0) {
- if (showToolDescriptions) {
- summaryText += ' (ctrl+t to toggle)';
- } else {
- summaryText += ' (ctrl+t to view)';
- }
+ summaryParts.push(mcpText);
+ }
+ summaryText += summaryParts.join(' | ');
+
+ // Add ctrl+t hint when MCP servers are available
+ if (mcpServers && Object.keys(mcpServers).length > 0) {
+ if (showToolDescriptions) {
+ summaryText += ' (ctrl+t to toggle)';
+ } else {
+ summaryText += ' (ctrl+t to view)';
}
}
diff --git a/packages/cli/src/ui/components/Footer.tsx b/packages/cli/src/ui/components/Footer.tsx
index 5524114b..95904cd9 100644
--- a/packages/cli/src/ui/components/Footer.tsx
+++ b/packages/cli/src/ui/components/Footer.tsx
@@ -4,16 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import React, { useEffect, useState } from 'react';
+import React from 'react';
import { Box, Text } from 'ink';
import { Colors } from '../colors.js';
-import {
- shortenPath,
- tildeifyPath,
- tokenLimit,
- ideContext,
- ActiveFile,
-} from '@google/gemini-cli-core';
+import { shortenPath, tildeifyPath, tokenLimit } from '@google/gemini-cli-core';
import { ConsoleSummaryDisplay } from './ConsoleSummaryDisplay.js';
import process from 'node:process';
import Gradient from 'ink-gradient';
@@ -49,24 +43,6 @@ export const Footer: React.FC = ({
const limit = tokenLimit(model);
const percentage = promptTokenCount / limit;
- const [activeFile, setActiveFile] = useState(
- undefined,
- );
-
- useEffect(() => {
- const updateActiveFile = () => {
- const currentActiveFile = ideContext.getActiveFileContext();
- setActiveFile(currentActiveFile);
- };
-
- updateActiveFile();
-
- const unsubscribe = ideContext.subscribeToActiveFile(setActiveFile);
- return () => {
- unsubscribe();
- };
- }, []);
-
return (
@@ -83,19 +59,6 @@ export const Footer: React.FC = ({
{branchName && ({branchName}*)}
)}
- {activeFile && activeFile.filePath && (
-
- |
-
- {shortenPath(tildeifyPath(activeFile.filePath), 70)}
-
- {activeFile.cursor && (
-
- :{activeFile.cursor.line}:{activeFile.cursor.character}
-
- )}
-
- )}
{debugMode && (
{' ' + (debugMessage || '--debug')}