| |
| |
| |
| |
| |
|
|
| import React from 'react'; |
| import { render } from 'ink'; |
| import { basename } from 'node:path'; |
| import { AppContainer } from './ui/AppContainer.js'; |
| import { ConsolePatcher } from './ui/utils/ConsolePatcher.js'; |
| import { |
| registerCleanup, |
| removeCleanup, |
| setupTtyCheck, |
| } from './utils/cleanup.js'; |
| import { |
| type StartupWarning, |
| type Config, |
| type ResumedSessionData, |
| coreEvents, |
| createWorkingStdio, |
| disableMouseEvents, |
| enableMouseEvents, |
| disableLineWrapping, |
| enableLineWrapping, |
| shouldEnterAlternateScreen, |
| recordSlowRender, |
| writeToStdout, |
| getVersion, |
| debugLogger, |
| } from '@google/gemini-cli-core'; |
| import type { InitializationResult } from './core/initializer.js'; |
| import type { LoadedSettings } from './config/settings.js'; |
| import { checkForUpdates } from './ui/utils/updateCheck.js'; |
| import { handleAutoUpdate } from './utils/handleAutoUpdate.js'; |
| import { SettingsContext } from './ui/contexts/SettingsContext.js'; |
| import { MouseProvider } from './ui/contexts/MouseContext.js'; |
| import { StreamingState } from './ui/types.js'; |
| import { computeTerminalTitle } from './utils/windowTitle.js'; |
|
|
| import { SessionStatsProvider } from './ui/contexts/SessionContext.js'; |
| import { VimModeProvider } from './ui/contexts/VimModeContext.js'; |
| import { KeyMatchersProvider } from './ui/hooks/useKeyMatchers.js'; |
| import { loadKeyMatchers } from './ui/key/keyMatchers.js'; |
| import { KeypressProvider } from './ui/contexts/KeypressContext.js'; |
| import { useKittyKeyboardProtocol } from './ui/hooks/useKittyKeyboardProtocol.js'; |
| import { ScrollProvider } from './ui/contexts/ScrollProvider.js'; |
| import { TerminalProvider } from './ui/contexts/TerminalContext.js'; |
| import { OverflowProvider } from './ui/contexts/OverflowContext.js'; |
| import { profiler } from './ui/components/DebugProfiler.js'; |
| import { initializeConsoleStore } from './ui/hooks/useConsoleMessages.js'; |
|
|
| const SLOW_RENDER_MS = 200; |
|
|
| export async function startInteractiveUI( |
| config: Config, |
| settings: LoadedSettings, |
| startupWarnings: StartupWarning[], |
| workspaceRoot: string = process.cwd(), |
| resumedSessionData: ResumedSessionData | undefined, |
| initializationResult: InitializationResult, |
| ) { |
| initializeConsoleStore(); |
| |
| |
| |
| |
| const useAlternateBuffer = shouldEnterAlternateScreen( |
| config.getUseAlternateBuffer(), |
| config.getScreenReader(), |
| ); |
| const mouseEventsEnabled = useAlternateBuffer; |
| if (mouseEventsEnabled) { |
| enableMouseEvents(); |
| registerCleanup(() => { |
| disableMouseEvents(); |
| }); |
| } |
|
|
| const { matchers, errors } = await loadKeyMatchers(); |
| errors.forEach((error) => { |
| coreEvents.emitFeedback('warning', error); |
| }); |
|
|
| const version = await getVersion(); |
| setWindowTitle(basename(workspaceRoot), settings); |
|
|
| const consolePatcher = new ConsolePatcher({ |
| onNewMessage: (msg) => { |
| coreEvents.emitConsoleLog(msg.type, msg.content); |
| }, |
| debugMode: config.getDebugMode(), |
| }); |
| consolePatcher.patch(); |
|
|
| const { stdout: inkStdout, stderr: inkStderr } = createWorkingStdio(); |
|
|
| const isShpool = !!process.env['SHPOOL_SESSION_NAME']; |
|
|
| |
| const AppWrapper = () => { |
| useKittyKeyboardProtocol(); |
|
|
| return ( |
| <SettingsContext.Provider value={settings}> |
| <KeyMatchersProvider value={matchers}> |
| <KeypressProvider config={config}> |
| <MouseProvider mouseEventsEnabled={mouseEventsEnabled}> |
| <TerminalProvider> |
| <ScrollProvider> |
| <OverflowProvider> |
| <SessionStatsProvider sessionId={config.getSessionId()}> |
| <VimModeProvider> |
| <AppContainer |
| config={config} |
| startupWarnings={startupWarnings} |
| version={version} |
| resumedSessionData={resumedSessionData} |
| initializationResult={initializationResult} |
| /> |
| </VimModeProvider> |
| </SessionStatsProvider> |
| </OverflowProvider> |
| </ScrollProvider> |
| </TerminalProvider> |
| </MouseProvider> |
| </KeypressProvider> |
| </KeyMatchersProvider> |
| </SettingsContext.Provider> |
| ); |
| }; |
|
|
| if (isShpool) { |
| |
| await new Promise((resolve) => setTimeout(resolve, 100)); |
| } |
| const instance = render( |
| process.env['DEBUG'] ? ( |
| <React.StrictMode> |
| <AppWrapper /> |
| </React.StrictMode> |
| ) : ( |
| <AppWrapper /> |
| ), |
| { |
| stdout: inkStdout, |
| stderr: inkStderr, |
| stdin: process.stdin, |
| exitOnCtrlC: false, |
| isScreenReaderEnabled: config.getScreenReader(), |
| onRender: ({ renderTime }: { renderTime: number }) => { |
| if (renderTime > SLOW_RENDER_MS) { |
| recordSlowRender(config, Math.round(renderTime)); |
| } |
| profiler.reportFrameRendered(); |
| }, |
| standardReactLayoutTiming: |
| useAlternateBuffer || config.getUseTerminalBuffer(), |
| patchConsole: false, |
| alternateBuffer: useAlternateBuffer, |
| terminalBuffer: config.getUseTerminalBuffer(), |
| renderProcess: |
| config.getUseRenderProcess() && config.getUseTerminalBuffer(), |
| incrementalRendering: |
| settings.merged.ui.incrementalRendering !== false && |
| useAlternateBuffer && |
| !isShpool, |
| debugRainbow: settings.merged.ui.debugRainbow === true, |
| }, |
| ); |
|
|
| let cleanupLineWrapping: (() => void) | undefined; |
| if (useAlternateBuffer) { |
| disableLineWrapping(); |
| cleanupLineWrapping = () => enableLineWrapping(); |
| registerCleanup(cleanupLineWrapping); |
| } |
|
|
| checkForUpdates(settings) |
| .then((info) => { |
| handleAutoUpdate( |
| info, |
| settings, |
| config.getProjectRoot(), |
| config.getSandboxEnabled(), |
| ); |
| }) |
| .catch((err) => { |
| |
| if (config.getDebugMode()) { |
| debugLogger.warn('Update check failed:', err); |
| } |
| }); |
|
|
| const cleanupUnmount = () => instance.unmount(); |
| const cleanupNonResumableCurrentSession = async () => { |
| try { |
| await config |
| .getGeminiClient() |
| ?.getChatRecordingService() |
| ?.deleteCurrentSessionIfNotResumableAsync(); |
| } catch (e: unknown) { |
| debugLogger.error('Error cleaning up non-resumable session:', e); |
| } |
| }; |
| registerCleanup(cleanupNonResumableCurrentSession); |
| registerCleanup(cleanupUnmount); |
|
|
| const cleanupTtyCheck = setupTtyCheck(); |
| registerCleanup(cleanupTtyCheck); |
|
|
| const cleanupConsolePatcher = () => consolePatcher.cleanup(); |
| registerCleanup(cleanupConsolePatcher); |
|
|
| try { |
| await instance.waitUntilExit(); |
| } finally { |
| try { |
| removeCleanup(cleanupConsolePatcher); |
| cleanupConsolePatcher(); |
| } catch (e: unknown) { |
| debugLogger.error('Error cleaning up console patcher:', e); |
| } |
|
|
| try { |
| removeCleanup(cleanupNonResumableCurrentSession); |
| await cleanupNonResumableCurrentSession(); |
| } catch (e: unknown) { |
| debugLogger.error('Error removing non-resumable session cleanup:', e); |
| } |
|
|
| try { |
| removeCleanup(cleanupUnmount); |
| instance.unmount(); |
| } catch (e: unknown) { |
| debugLogger.error('Error unmounting Ink instance:', e); |
| } |
|
|
| try { |
| removeCleanup(cleanupTtyCheck); |
| cleanupTtyCheck(); |
| } catch (e: unknown) { |
| debugLogger.error('Error in TTY cleanup:', e); |
| } |
|
|
| if (cleanupLineWrapping) { |
| try { |
| removeCleanup(cleanupLineWrapping); |
| cleanupLineWrapping(); |
| } catch (e: unknown) { |
| debugLogger.error('Error restoring line wrapping:', e); |
| } |
| } |
| } |
| } |
|
|
| function setWindowTitle(title: string, settings: LoadedSettings) { |
| if (!settings.merged.ui.hideWindowTitle) { |
| |
| const windowTitle = computeTerminalTitle({ |
| streamingState: StreamingState.Idle, |
| isConfirming: false, |
| isSilentWorking: false, |
| folderName: title, |
| showThoughts: !!settings.merged.ui.showStatusInTitle, |
| useDynamicTitle: settings.merged.ui.dynamicWindowTitle, |
| }); |
| writeToStdout(`\x1b]0;${windowTitle}\x07`); |
|
|
| process.on('exit', () => { |
| writeToStdout(`\x1b]0;\x07`); |
| }); |
| } |
| } |
|
|