mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-22 19:14:33 -07:00
refactor(core,cli): useAlternateBuffer read from config (#20346)
Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
@@ -843,6 +843,7 @@ export async function loadCliConfig(
|
||||
interactive,
|
||||
trustedFolder,
|
||||
useBackgroundColor: settings.ui?.useBackgroundColor,
|
||||
useAlternateBuffer: settings.ui?.useAlternateBuffer,
|
||||
useRipgrep: settings.tools?.useRipgrep,
|
||||
enableInteractiveShell: settings.tools?.shell?.enableInteractiveShell,
|
||||
shellToolInactivityTimeout: settings.tools?.shell?.inactivityTimeout,
|
||||
|
||||
@@ -1182,6 +1182,7 @@ describe('startInteractiveUI', () => {
|
||||
getProjectRoot: () => '/root',
|
||||
getScreenReader: () => false,
|
||||
getDebugMode: () => false,
|
||||
getUseAlternateBuffer: () => true,
|
||||
});
|
||||
const mockSettings = {
|
||||
merged: {
|
||||
|
||||
@@ -102,8 +102,8 @@ import { loadSandboxConfig } from './config/sandboxConfig.js';
|
||||
import { deleteSession, listSessions } from './utils/sessions.js';
|
||||
import { createPolicyUpdater } from './config/policy.js';
|
||||
import { ScrollProvider } from './ui/contexts/ScrollProvider.js';
|
||||
import { isAlternateBufferEnabled } from './ui/hooks/useAlternateBuffer.js';
|
||||
import { TerminalProvider } from './ui/contexts/TerminalContext.js';
|
||||
import { isAlternateBufferEnabled } from './ui/hooks/useAlternateBuffer.js';
|
||||
import { OverflowProvider } from './ui/contexts/OverflowContext.js';
|
||||
|
||||
import { setupTerminalAndTheme } from './utils/terminalTheme.js';
|
||||
@@ -196,7 +196,7 @@ export async function startInteractiveUI(
|
||||
// and the Ink alternate buffer mode requires line wrapping harmful to
|
||||
// screen readers.
|
||||
const useAlternateBuffer = shouldEnterAlternateScreen(
|
||||
isAlternateBufferEnabled(settings),
|
||||
isAlternateBufferEnabled(config),
|
||||
config.getScreenReader(),
|
||||
);
|
||||
const mouseEventsEnabled = useAlternateBuffer;
|
||||
@@ -678,7 +678,7 @@ export async function main() {
|
||||
|
||||
let input = config.getQuestion();
|
||||
const useAlternateBuffer = shouldEnterAlternateScreen(
|
||||
isAlternateBufferEnabled(settings),
|
||||
isAlternateBufferEnabled(config),
|
||||
config.getScreenReader(),
|
||||
);
|
||||
const rawStartupWarnings = await getStartupWarnings();
|
||||
|
||||
@@ -156,6 +156,7 @@ export const createMockConfig = (overrides: Partial<Config> = {}): Config =>
|
||||
getExperiments: vi.fn().mockReturnValue(undefined),
|
||||
getHasAccessToPreviewModel: vi.fn().mockReturnValue(false),
|
||||
validatePathAccess: vi.fn().mockReturnValue(null),
|
||||
getUseAlternateBuffer: vi.fn().mockReturnValue(false),
|
||||
...overrides,
|
||||
}) as unknown as Config;
|
||||
|
||||
|
||||
@@ -703,6 +703,21 @@ export const renderWithProviders = (
|
||||
});
|
||||
}
|
||||
|
||||
// Wrap config in a Proxy so useAlternateBuffer hook (which reads from Config) gets the correct value,
|
||||
// without replacing the entire config object and its other values.
|
||||
let finalConfig = config;
|
||||
if (useAlternateBuffer !== undefined) {
|
||||
finalConfig = new Proxy(config, {
|
||||
get(target, prop, receiver) {
|
||||
if (prop === 'getUseAlternateBuffer') {
|
||||
return () => useAlternateBuffer;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return Reflect.get(target, prop, receiver);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const mainAreaWidth = terminalWidth;
|
||||
|
||||
const finalUiState = {
|
||||
@@ -731,7 +746,7 @@ export const renderWithProviders = (
|
||||
|
||||
const renderResult = render(
|
||||
<AppContext.Provider value={appState}>
|
||||
<ConfigContext.Provider value={config}>
|
||||
<ConfigContext.Provider value={finalConfig}>
|
||||
<SettingsContext.Provider value={finalSettings}>
|
||||
<UIStateContext.Provider value={finalUiState}>
|
||||
<VimModeProvider settings={finalSettings}>
|
||||
@@ -743,7 +758,7 @@ export const renderWithProviders = (
|
||||
<UIActionsContext.Provider value={finalUIActions}>
|
||||
<OverflowProvider>
|
||||
<ToolActionsProvider
|
||||
config={config}
|
||||
config={finalConfig}
|
||||
toolCalls={allToolCalls}
|
||||
>
|
||||
<AskUserActionsProvider
|
||||
|
||||
@@ -2675,6 +2675,10 @@ describe('AppContainer State Management', () => {
|
||||
isAlternateMode = false,
|
||||
childHandler?: Mock,
|
||||
) => {
|
||||
vi.spyOn(mockConfig, 'getUseAlternateBuffer').mockReturnValue(
|
||||
isAlternateMode,
|
||||
);
|
||||
|
||||
// Update settings for this test run
|
||||
const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
|
||||
const testSettings = {
|
||||
@@ -3364,6 +3368,8 @@ describe('AppContainer State Management', () => {
|
||||
);
|
||||
vi.mocked(checkPermissions).mockResolvedValue([]);
|
||||
|
||||
vi.spyOn(mockConfig, 'getUseAlternateBuffer').mockReturnValue(true);
|
||||
|
||||
let unmount: () => void;
|
||||
await act(async () => {
|
||||
unmount = renderAppContainer({
|
||||
@@ -3596,6 +3602,8 @@ describe('AppContainer State Management', () => {
|
||||
},
|
||||
} as unknown as LoadedSettings;
|
||||
|
||||
vi.spyOn(mockConfig, 'getUseAlternateBuffer').mockReturnValue(true);
|
||||
|
||||
let unmount: () => void;
|
||||
await act(async () => {
|
||||
const result = renderAppContainer({
|
||||
|
||||
@@ -145,7 +145,6 @@ import { useSessionResume } from './hooks/useSessionResume.js';
|
||||
import { useIncludeDirsTrust } from './hooks/useIncludeDirsTrust.js';
|
||||
import { useSessionRetentionCheck } from './hooks/useSessionRetentionCheck.js';
|
||||
import { isWorkspaceTrusted } from '../config/trustedFolders.js';
|
||||
import { useAlternateBuffer } from './hooks/useAlternateBuffer.js';
|
||||
import { useSettings } from './contexts/SettingsContext.js';
|
||||
import { terminalCapabilityManager } from './utils/terminalCapabilityManager.js';
|
||||
import { useInputHistoryStore } from './hooks/useInputHistoryStore.js';
|
||||
@@ -228,7 +227,7 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
});
|
||||
|
||||
useMemoryMonitor(historyManager);
|
||||
const isAlternateBuffer = useAlternateBuffer();
|
||||
const isAlternateBuffer = config.getUseAlternateBuffer();
|
||||
const [corgiMode, setCorgiMode] = useState(false);
|
||||
const [forceRerenderKey, setForceRerenderKey] = useState(0);
|
||||
const [debugMessage, setDebugMessage] = useState<string>('');
|
||||
@@ -545,7 +544,7 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
const { consoleMessages, clearConsoleMessages: clearConsoleMessagesState } =
|
||||
useConsoleMessages();
|
||||
|
||||
const mainAreaWidth = calculateMainAreaWidth(terminalWidth, settings);
|
||||
const mainAreaWidth = calculateMainAreaWidth(terminalWidth, config);
|
||||
// Derive widths for InputPrompt using shared helper
|
||||
const { inputWidth, suggestionsWidth } = useMemo(() => {
|
||||
const { inputWidth, suggestionsWidth } =
|
||||
|
||||
@@ -167,6 +167,7 @@ Implement a comprehensive authentication system with multiple providers.
|
||||
readTextFile: vi.fn(),
|
||||
writeTextFile: vi.fn(),
|
||||
}),
|
||||
getUseAlternateBuffer: () => options?.useAlternateBuffer ?? true,
|
||||
} as unknown as import('@google/gemini-cli-core').Config,
|
||||
},
|
||||
);
|
||||
@@ -443,6 +444,7 @@ Implement a comprehensive authentication system with multiple providers.
|
||||
readTextFile: vi.fn(),
|
||||
writeTextFile: vi.fn(),
|
||||
}),
|
||||
getUseAlternateBuffer: () => useAlternateBuffer ?? true,
|
||||
} as unknown as import('@google/gemini-cli-core').Config,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -51,6 +51,7 @@ describe('ToolConfirmationQueue', () => {
|
||||
storage: {
|
||||
getPlansDir: () => '/mock/temp/plans',
|
||||
},
|
||||
getUseAlternateBuffer: () => false,
|
||||
} as unknown as Config;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import {
|
||||
useAlternateBuffer,
|
||||
isAlternateBufferEnabled,
|
||||
} from './useAlternateBuffer.js';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
|
||||
vi.mock('../contexts/ConfigContext.js', () => ({
|
||||
useConfig: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockUseConfig = vi.mocked(
|
||||
await import('../contexts/ConfigContext.js').then((m) => m.useConfig),
|
||||
);
|
||||
|
||||
describe('useAlternateBuffer', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return false when config.getUseAlternateBuffer returns false', () => {
|
||||
mockUseConfig.mockReturnValue({
|
||||
getUseAlternateBuffer: () => false,
|
||||
} as unknown as ReturnType<typeof mockUseConfig>);
|
||||
|
||||
const { result } = renderHook(() => useAlternateBuffer());
|
||||
expect(result.current).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when config.getUseAlternateBuffer returns true', () => {
|
||||
mockUseConfig.mockReturnValue({
|
||||
getUseAlternateBuffer: () => true,
|
||||
} as unknown as ReturnType<typeof mockUseConfig>);
|
||||
|
||||
const { result } = renderHook(() => useAlternateBuffer());
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
|
||||
it('should return the immutable config value, not react to settings changes', () => {
|
||||
const mockConfig = {
|
||||
getUseAlternateBuffer: () => true,
|
||||
} as unknown as ReturnType<typeof mockUseConfig>;
|
||||
|
||||
mockUseConfig.mockReturnValue(mockConfig);
|
||||
|
||||
const { result, rerender } = renderHook(() => useAlternateBuffer());
|
||||
|
||||
// Value should remain true even after rerender
|
||||
expect(result.current).toBe(true);
|
||||
|
||||
rerender();
|
||||
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAlternateBufferEnabled', () => {
|
||||
it('should return true when config.getUseAlternateBuffer returns true', () => {
|
||||
const config = {
|
||||
getUseAlternateBuffer: () => true,
|
||||
} as unknown as Config;
|
||||
|
||||
expect(isAlternateBufferEnabled(config)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when config.getUseAlternateBuffer returns false', () => {
|
||||
const config = {
|
||||
getUseAlternateBuffer: () => false,
|
||||
} as unknown as Config;
|
||||
|
||||
expect(isAlternateBufferEnabled(config)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -4,13 +4,14 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useSettings } from '../contexts/SettingsContext.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import { useConfig } from '../contexts/ConfigContext.js';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
|
||||
export const isAlternateBufferEnabled = (settings: LoadedSettings): boolean =>
|
||||
settings.merged.ui.useAlternateBuffer === true;
|
||||
export const isAlternateBufferEnabled = (config: Config): boolean =>
|
||||
config.getUseAlternateBuffer();
|
||||
|
||||
// This is read from Config so that the UI reads the same value per application session
|
||||
export const useAlternateBuffer = (): boolean => {
|
||||
const settings = useSettings();
|
||||
return isAlternateBufferEnabled(settings);
|
||||
const config = useConfig();
|
||||
return isAlternateBufferEnabled(config);
|
||||
};
|
||||
|
||||
@@ -4,29 +4,11 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { calculateMainAreaWidth } from './ui-sizing.js';
|
||||
import { type LoadedSettings } from '../../config/settings.js';
|
||||
|
||||
// Mock dependencies
|
||||
const mocks = vi.hoisted(() => ({
|
||||
isAlternateBufferEnabled: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../hooks/useAlternateBuffer.js', () => ({
|
||||
isAlternateBufferEnabled: mocks.isAlternateBufferEnabled,
|
||||
}));
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
|
||||
describe('ui-sizing', () => {
|
||||
const createSettings = (useFullWidth?: boolean): LoadedSettings =>
|
||||
({
|
||||
merged: {
|
||||
ui: {
|
||||
useFullWidth,
|
||||
},
|
||||
},
|
||||
}) as unknown as LoadedSettings;
|
||||
|
||||
describe('calculateMainAreaWidth', () => {
|
||||
it.each([
|
||||
// expected, width, altBuffer
|
||||
@@ -37,10 +19,10 @@ describe('ui-sizing', () => {
|
||||
])(
|
||||
'should return %i when width=%i and altBuffer=%s',
|
||||
(expected, width, altBuffer) => {
|
||||
mocks.isAlternateBufferEnabled.mockReturnValue(altBuffer);
|
||||
const settings = createSettings();
|
||||
|
||||
expect(calculateMainAreaWidth(width, settings)).toBe(expected);
|
||||
const mockConfig = {
|
||||
getUseAlternateBuffer: () => altBuffer,
|
||||
} as unknown as Config;
|
||||
expect(calculateMainAreaWidth(width, mockConfig)).toBe(expected);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { type LoadedSettings } from '../../config/settings.js';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
import { isAlternateBufferEnabled } from '../hooks/useAlternateBuffer.js';
|
||||
|
||||
export const calculateMainAreaWidth = (
|
||||
terminalWidth: number,
|
||||
settings: LoadedSettings,
|
||||
config: Config,
|
||||
): number => {
|
||||
if (isAlternateBufferEnabled(settings)) {
|
||||
if (isAlternateBufferEnabled(config)) {
|
||||
return terminalWidth - 1;
|
||||
}
|
||||
return terminalWidth;
|
||||
|
||||
Reference in New Issue
Block a user