mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-21 19:40:40 -07:00
118 lines
3.1 KiB
TypeScript
118 lines
3.1 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { useStdin, useStdout } from 'ink';
|
|
import type React from 'react';
|
|
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useRef,
|
|
} from 'react';
|
|
import { TerminalCapabilityManager } from '../utils/terminalCapabilityManager.js';
|
|
|
|
export type TerminalEventHandler = (event: string) => void;
|
|
|
|
interface TerminalContextValue {
|
|
subscribe: (handler: TerminalEventHandler) => void;
|
|
unsubscribe: (handler: TerminalEventHandler) => void;
|
|
queryTerminalBackground: () => Promise<void>;
|
|
}
|
|
|
|
const TerminalContext = createContext<TerminalContextValue | undefined>(
|
|
undefined,
|
|
);
|
|
|
|
export function useTerminalContext() {
|
|
const context = useContext(TerminalContext);
|
|
if (!context) {
|
|
throw new Error(
|
|
'useTerminalContext must be used within a TerminalProvider',
|
|
);
|
|
}
|
|
return context;
|
|
}
|
|
|
|
export function TerminalProvider({ children }: { children: React.ReactNode }) {
|
|
const { stdin } = useStdin();
|
|
const { stdout } = useStdout();
|
|
const subscribers = useRef<Set<TerminalEventHandler>>(new Set()).current;
|
|
const bufferRef = useRef('');
|
|
|
|
const subscribe = useCallback(
|
|
(handler: TerminalEventHandler) => {
|
|
subscribers.add(handler);
|
|
},
|
|
[subscribers],
|
|
);
|
|
|
|
const unsubscribe = useCallback(
|
|
(handler: TerminalEventHandler) => {
|
|
subscribers.delete(handler);
|
|
},
|
|
[subscribers],
|
|
);
|
|
|
|
const queryTerminalBackground = useCallback(
|
|
async () =>
|
|
new Promise<void>((resolve) => {
|
|
const handler = () => {
|
|
unsubscribe(handler);
|
|
resolve();
|
|
};
|
|
subscribe(handler);
|
|
TerminalCapabilityManager.queryBackgroundColor(stdout);
|
|
setTimeout(() => {
|
|
unsubscribe(handler);
|
|
resolve();
|
|
}, 100);
|
|
}),
|
|
[stdout, subscribe, unsubscribe],
|
|
);
|
|
|
|
useEffect(() => {
|
|
const handleData = (data: Buffer | string) => {
|
|
bufferRef.current +=
|
|
typeof data === 'string' ? data : data.toString('utf-8');
|
|
|
|
// Check for OSC 11 response
|
|
const match = bufferRef.current.match(
|
|
TerminalCapabilityManager.OSC_11_REGEX,
|
|
);
|
|
if (match) {
|
|
const colorStr = `rgb:${match[1]}/${match[2]}/${match[3]}`;
|
|
for (const handler of subscribers) {
|
|
handler(colorStr);
|
|
}
|
|
// Safely remove the processed part + match
|
|
if (match.index !== undefined) {
|
|
bufferRef.current = bufferRef.current.slice(
|
|
match.index + match[0].length,
|
|
);
|
|
}
|
|
} else if (bufferRef.current.length > 4096) {
|
|
// Safety valve: if buffer gets too large without a match, trim it.
|
|
// We keep the last 1024 bytes to avoid cutting off a partial sequence.
|
|
bufferRef.current = bufferRef.current.slice(-1024);
|
|
}
|
|
};
|
|
|
|
stdin.on('data', handleData);
|
|
return () => {
|
|
stdin.removeListener('data', handleData);
|
|
};
|
|
}, [stdin, subscribers]);
|
|
|
|
return (
|
|
<TerminalContext.Provider
|
|
value={{ subscribe, unsubscribe, queryTerminalBackground }}
|
|
>
|
|
{children}
|
|
</TerminalContext.Provider>
|
|
);
|
|
}
|