mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-21 19:40:40 -07:00
Support ink scrolling final pr (#12567)
This commit is contained in:
@@ -119,28 +119,38 @@ export function colorizeLine(
|
||||
return highlightAndRenderLine(line, language, activeTheme);
|
||||
}
|
||||
|
||||
export interface ColorizeCodeOptions {
|
||||
code: string;
|
||||
language?: string | null;
|
||||
availableHeight?: number;
|
||||
maxWidth: number;
|
||||
theme?: Theme | null;
|
||||
settings: LoadedSettings;
|
||||
hideLineNumbers?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders syntax-highlighted code for Ink applications using a selected theme.
|
||||
*
|
||||
* @param code The code string to highlight.
|
||||
* @param language The language identifier (e.g., 'javascript', 'css', 'html')
|
||||
* @param options The options for colorizing the code.
|
||||
* @returns A React.ReactNode containing Ink <Text> elements for the highlighted code.
|
||||
*/
|
||||
export function colorizeCode(
|
||||
code: string,
|
||||
language: string | null,
|
||||
availableHeight?: number,
|
||||
maxWidth?: number,
|
||||
theme?: Theme,
|
||||
settings?: LoadedSettings,
|
||||
hideLineNumbers?: boolean,
|
||||
): React.ReactNode {
|
||||
export function colorizeCode({
|
||||
code,
|
||||
language = null,
|
||||
availableHeight,
|
||||
maxWidth,
|
||||
theme = null,
|
||||
settings,
|
||||
hideLineNumbers = false,
|
||||
}: ColorizeCodeOptions): React.ReactNode {
|
||||
const codeToHighlight = code.replace(/\n$/, '');
|
||||
const activeTheme = theme || themeManager.getActiveTheme();
|
||||
const showLineNumbers = hideLineNumbers
|
||||
? false
|
||||
: (settings?.merged.ui?.showLineNumbers ?? true);
|
||||
|
||||
const useMaxSizedBox = settings?.merged.ui?.useAlternateBuffer !== true;
|
||||
try {
|
||||
// Render the HAST tree using the adapted theme
|
||||
// Apply the theme's default foreground color to the top-level Text element
|
||||
@@ -150,7 +160,10 @@ export function colorizeCode(
|
||||
let hiddenLinesCount = 0;
|
||||
|
||||
// Optimization to avoid highlighting lines that cannot possibly be displayed.
|
||||
if (availableHeight !== undefined) {
|
||||
if (
|
||||
availableHeight !== undefined &&
|
||||
settings?.merged.ui?.useAlternateBuffer === false
|
||||
) {
|
||||
availableHeight = Math.max(availableHeight, MINIMUM_MAX_HEIGHT);
|
||||
if (lines.length > availableHeight) {
|
||||
const sliceIndex = lines.length - availableHeight;
|
||||
@@ -159,37 +172,61 @@ export function colorizeCode(
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<MaxSizedBox
|
||||
maxHeight={availableHeight}
|
||||
maxWidth={maxWidth}
|
||||
additionalHiddenLinesCount={hiddenLinesCount}
|
||||
overflowDirection="top"
|
||||
>
|
||||
{lines.map((line, index) => {
|
||||
const contentToRender = highlightAndRenderLine(
|
||||
line,
|
||||
language,
|
||||
activeTheme,
|
||||
);
|
||||
const renderedLines = lines.map((line, index) => {
|
||||
const contentToRender = highlightAndRenderLine(
|
||||
line,
|
||||
language,
|
||||
activeTheme,
|
||||
);
|
||||
|
||||
return (
|
||||
<Box key={index}>
|
||||
{showLineNumbers && (
|
||||
<Text color={activeTheme.colors.Gray}>
|
||||
{`${String(index + 1 + hiddenLinesCount).padStart(
|
||||
padWidth,
|
||||
' ',
|
||||
)} `}
|
||||
</Text>
|
||||
)}
|
||||
<Text color={activeTheme.defaultColor} wrap="wrap">
|
||||
{contentToRender}
|
||||
return (
|
||||
<Box key={index}>
|
||||
{/* We have to render line numbers differently depending on whether we are using MaxSizeBox or not */}
|
||||
{showLineNumbers && useMaxSizedBox && (
|
||||
<Text color={activeTheme.colors.Gray}>
|
||||
{`${String(index + 1 + hiddenLinesCount).padStart(
|
||||
padWidth,
|
||||
' ',
|
||||
)} `}
|
||||
</Text>
|
||||
)}
|
||||
{showLineNumbers && !useMaxSizedBox && (
|
||||
<Box
|
||||
minWidth={padWidth + 1}
|
||||
flexShrink={0}
|
||||
paddingRight={1}
|
||||
alignItems="flex-start"
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Text color={activeTheme.colors.Gray}>
|
||||
{`${index + 1 + hiddenLinesCount}`}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</MaxSizedBox>
|
||||
)}
|
||||
<Text color={activeTheme.defaultColor} wrap="wrap">
|
||||
{contentToRender}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
if (useMaxSizedBox) {
|
||||
return (
|
||||
<MaxSizedBox
|
||||
maxHeight={availableHeight}
|
||||
maxWidth={maxWidth}
|
||||
additionalHiddenLinesCount={hiddenLinesCount}
|
||||
overflowDirection="top"
|
||||
>
|
||||
{renderedLines}
|
||||
</MaxSizedBox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" width={maxWidth}>
|
||||
{renderedLines}
|
||||
</Box>
|
||||
);
|
||||
} catch (error) {
|
||||
debugLogger.warn(
|
||||
@@ -200,23 +237,45 @@ export function colorizeCode(
|
||||
// Also display line numbers in fallback
|
||||
const lines = codeToHighlight.split('\n');
|
||||
const padWidth = String(lines.length).length; // Calculate padding width based on number of lines
|
||||
return (
|
||||
<MaxSizedBox
|
||||
maxHeight={availableHeight}
|
||||
maxWidth={maxWidth}
|
||||
overflowDirection="top"
|
||||
>
|
||||
{lines.map((line, index) => (
|
||||
<Box key={index}>
|
||||
{showLineNumbers && (
|
||||
<Text color={activeTheme.defaultColor}>
|
||||
{`${String(index + 1).padStart(padWidth, ' ')} `}
|
||||
</Text>
|
||||
)}
|
||||
<Text color={activeTheme.colors.Gray}>{line}</Text>
|
||||
const fallbackLines = lines.map((line, index) => (
|
||||
<Box key={index}>
|
||||
{/* We have to render line numbers differently depending on whether we are using MaxSizeBox or not */}
|
||||
{showLineNumbers && useMaxSizedBox && (
|
||||
<Text color={activeTheme.defaultColor}>
|
||||
{`${String(index + 1).padStart(padWidth, ' ')} `}
|
||||
</Text>
|
||||
)}
|
||||
{showLineNumbers && !useMaxSizedBox && (
|
||||
<Box
|
||||
minWidth={padWidth + 1}
|
||||
flexShrink={0}
|
||||
paddingRight={1}
|
||||
alignItems="flex-start"
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Text color={activeTheme.defaultColor}>{`${index + 1}`}</Text>
|
||||
</Box>
|
||||
))}
|
||||
</MaxSizedBox>
|
||||
)}
|
||||
<Text color={activeTheme.colors.Gray}>{line}</Text>
|
||||
</Box>
|
||||
));
|
||||
|
||||
if (useMaxSizedBox) {
|
||||
return (
|
||||
<MaxSizedBox
|
||||
maxHeight={availableHeight}
|
||||
maxWidth={maxWidth}
|
||||
overflowDirection="top"
|
||||
>
|
||||
{fallbackLines}
|
||||
</MaxSizedBox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" width={maxWidth}>
|
||||
{fallbackLines}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { colorizeCode } from './CodeColorizer.js';
|
||||
import { TableRenderer } from './TableRenderer.js';
|
||||
import { RenderInline } from './InlineMarkdownRenderer.js';
|
||||
import { useSettings } from '../contexts/SettingsContext.js';
|
||||
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
|
||||
|
||||
interface MarkdownDisplayProps {
|
||||
text: string;
|
||||
@@ -35,6 +36,7 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({
|
||||
renderMarkdown = true,
|
||||
}) => {
|
||||
const settings = useSettings();
|
||||
const isAlternateBuffer = useAlternateBuffer();
|
||||
const responseColor = theme.text.response ?? theme.text.primary;
|
||||
|
||||
if (!text) return <></>;
|
||||
@@ -42,15 +44,14 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({
|
||||
// Raw markdown mode - display syntax-highlighted markdown without rendering
|
||||
if (!renderMarkdown) {
|
||||
// Hide line numbers in raw markdown mode as they are confusing due to chunked output
|
||||
const colorizedMarkdown = colorizeCode(
|
||||
text,
|
||||
'markdown',
|
||||
availableTerminalHeight,
|
||||
terminalWidth - CODE_BLOCK_PREFIX_PADDING,
|
||||
undefined,
|
||||
const colorizedMarkdown = colorizeCode({
|
||||
code: text,
|
||||
language: 'markdown',
|
||||
availableHeight: isAlternateBuffer ? undefined : availableTerminalHeight,
|
||||
maxWidth: terminalWidth - CODE_BLOCK_PREFIX_PADDING,
|
||||
settings,
|
||||
true, // hideLineNumbers
|
||||
);
|
||||
hideLineNumbers: true,
|
||||
});
|
||||
return (
|
||||
<Box paddingLeft={CODE_BLOCK_PREFIX_PADDING} flexDirection="column">
|
||||
{colorizedMarkdown}
|
||||
@@ -100,7 +101,9 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({
|
||||
content={codeBlockContent}
|
||||
lang={codeBlockLang}
|
||||
isPending={isPending}
|
||||
availableTerminalHeight={availableTerminalHeight}
|
||||
availableTerminalHeight={
|
||||
isAlternateBuffer ? undefined : availableTerminalHeight
|
||||
}
|
||||
terminalWidth={terminalWidth}
|
||||
/>,
|
||||
);
|
||||
@@ -288,7 +291,9 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({
|
||||
content={codeBlockContent}
|
||||
lang={codeBlockLang}
|
||||
isPending={isPending}
|
||||
availableTerminalHeight={availableTerminalHeight}
|
||||
availableTerminalHeight={
|
||||
isAlternateBuffer ? undefined : availableTerminalHeight
|
||||
}
|
||||
terminalWidth={terminalWidth}
|
||||
/>,
|
||||
);
|
||||
@@ -327,10 +332,17 @@ const RenderCodeBlockInternal: React.FC<RenderCodeBlockProps> = ({
|
||||
terminalWidth,
|
||||
}) => {
|
||||
const settings = useSettings();
|
||||
const isAlternateBuffer = useAlternateBuffer();
|
||||
const MIN_LINES_FOR_MESSAGE = 1; // Minimum lines to show before the "generating more" message
|
||||
const RESERVED_LINES = 2; // Lines reserved for the message itself and potential padding
|
||||
|
||||
if (isPending && availableTerminalHeight !== undefined) {
|
||||
// When not in alternate buffer mode we need to be careful that we don't
|
||||
// trigger flicker when the pending code is to long to fit in the terminal
|
||||
if (
|
||||
!isAlternateBuffer &&
|
||||
isPending &&
|
||||
availableTerminalHeight !== undefined
|
||||
) {
|
||||
const MAX_CODE_LINES_WHEN_PENDING = Math.max(
|
||||
0,
|
||||
availableTerminalHeight - RESERVED_LINES,
|
||||
@@ -348,14 +360,13 @@ const RenderCodeBlockInternal: React.FC<RenderCodeBlockProps> = ({
|
||||
);
|
||||
}
|
||||
const truncatedContent = content.slice(0, MAX_CODE_LINES_WHEN_PENDING);
|
||||
const colorizedTruncatedCode = colorizeCode(
|
||||
truncatedContent.join('\n'),
|
||||
lang,
|
||||
availableTerminalHeight,
|
||||
terminalWidth - CODE_BLOCK_PREFIX_PADDING,
|
||||
undefined,
|
||||
const colorizedTruncatedCode = colorizeCode({
|
||||
code: truncatedContent.join('\n'),
|
||||
language: lang,
|
||||
availableHeight: availableTerminalHeight,
|
||||
maxWidth: terminalWidth - CODE_BLOCK_PREFIX_PADDING,
|
||||
settings,
|
||||
);
|
||||
});
|
||||
return (
|
||||
<Box paddingLeft={CODE_BLOCK_PREFIX_PADDING} flexDirection="column">
|
||||
{colorizedTruncatedCode}
|
||||
@@ -366,14 +377,13 @@ const RenderCodeBlockInternal: React.FC<RenderCodeBlockProps> = ({
|
||||
}
|
||||
|
||||
const fullContent = content.join('\n');
|
||||
const colorizedCode = colorizeCode(
|
||||
fullContent,
|
||||
lang,
|
||||
availableTerminalHeight,
|
||||
terminalWidth - CODE_BLOCK_PREFIX_PADDING,
|
||||
undefined,
|
||||
const colorizedCode = colorizeCode({
|
||||
code: fullContent,
|
||||
language: lang,
|
||||
availableHeight: isAlternateBuffer ? undefined : availableTerminalHeight,
|
||||
maxWidth: terminalWidth - CODE_BLOCK_PREFIX_PADDING,
|
||||
settings,
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Box
|
||||
|
||||
@@ -25,7 +25,12 @@ const getMainAreaWidthInternal = (terminalWidth: number): number => {
|
||||
export const calculateMainAreaWidth = (
|
||||
terminalWidth: number,
|
||||
settings: LoadedSettings,
|
||||
): number =>
|
||||
settings.merged.ui?.useFullWidth
|
||||
? terminalWidth
|
||||
: getMainAreaWidthInternal(terminalWidth);
|
||||
): number => {
|
||||
if (settings.merged.ui?.useFullWidth) {
|
||||
if (settings.merged.ui?.useAlternateBuffer) {
|
||||
return terminalWidth - 1;
|
||||
}
|
||||
return terminalWidth;
|
||||
}
|
||||
return getMainAreaWidthInternal(terminalWidth);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user