Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/cli/src/ui/components/AnsiOutput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ export const AnsiOutputText: React.FC<AnsiOutputProps> = ({
? Math.min(availableHeightLimit, maxLines)
: (availableHeightLimit ?? maxLines ?? DEFAULT_HEIGHT);

const lastLines = disableTruncation ? data : data.slice(-numLinesRetained);
const lastLines = disableTruncation
? data
: numLinesRetained === 0
? []
: data.slice(-numLinesRetained);
return (
<Box flexDirection="column" width={width} flexShrink={0} overflow="hidden">
{lastLines.map((line: AnsiLine, lineIndex: number) => (
Expand Down
5 changes: 3 additions & 2 deletions packages/cli/src/ui/components/MainContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const MainContent = () => {
pendingHistoryItems,
mainAreaWidth,
staticAreaMaxItemHeight,
availableTerminalHeight,
cleanUiDetailsVisible,
} = uiState;
const showHeaderDetails = cleanUiDetailsVisible;
Expand Down Expand Up @@ -141,7 +142,7 @@ export const MainContent = () => {
<HistoryItemDisplay
key={i}
availableTerminalHeight={
uiState.constrainHeight ? staticAreaMaxItemHeight : undefined
uiState.constrainHeight ? availableTerminalHeight : undefined
}
terminalWidth={mainAreaWidth}
item={{ ...item, id: 0 }}
Expand All @@ -160,7 +161,7 @@ export const MainContent = () => {
[
pendingHistoryItems,
uiState.constrainHeight,
staticAreaMaxItemHeight,
availableTerminalHeight,
mainAreaWidth,
showConfirmationQueue,
confirmingTool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ AppHeader(full)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
│ ⊶ Shell Command Running a long command... │
│ │
│ Line 9 │
│ Line 10 │
│ Line 11 │
│ Line 12 │
│ Line 13 │
│ Line 14
│ Line 14
│ Line 15 █ │
│ Line 16 █ │
│ Line 17 █ │
Expand All @@ -27,11 +28,12 @@ AppHeader(full)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
│ ⊶ Shell Command Running a long command... │
│ │
│ Line 9 │
│ Line 10 │
│ Line 11 │
│ Line 12 │
│ Line 13 │
│ Line 14
│ Line 14
│ Line 15 █ │
│ Line 16 █ │
│ Line 17 █ │
Expand All @@ -47,7 +49,9 @@ exports[`MainContent > MainContent Tool Output Height Logic > 'Normal mode - Con
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
│ ⊶ Shell Command Running a long command... │
│ │
│ ... first 11 lines hidden (Ctrl+O to show) ... │
│ ... first 9 lines hidden (Ctrl+O to show) ... │
│ Line 10 │
│ Line 11 │
│ Line 12 │
│ Line 13 │
│ Line 14 │
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ describe('<ShellToolMessage />', () => {
[
'uses full availableTerminalHeight when focused in alternate buffer mode',
100,
98, // 100 - 2
98,
true,
false,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function SlicingMaxSizedBox<T>({
text = '...' + text.slice(-MAXIMUM_RESULT_DISPLAY_CHARACTERS);
}
}
if (maxLines) {
if (maxLines !== undefined) {
const hasTrailingNewline = text.endsWith('\n');
const contentText = hasTrailingNewline ? text.slice(0, -1) : text;
const lines = contentText.split('\n');
Expand All @@ -71,7 +71,7 @@ export function SlicingMaxSizedBox<T>({
};
}

if (Array.isArray(data) && !isAlternateBuffer && maxLines) {
if (Array.isArray(data) && !isAlternateBuffer && maxLines !== undefined) {
if (data.length > maxLines) {
// We will have a label from MaxSizedBox. Reserve space for it.
const targetLines = Math.max(1, maxLines - 1);
Expand Down
208 changes: 208 additions & 0 deletions packages/cli/src/ui/utils/toolLayoutUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { describe, it, expect } from 'vitest';
import {
calculateToolContentMaxLines,
calculateShellMaxLines,
SHELL_CONTENT_OVERHEAD,
} from './toolLayoutUtils.js';
import { CoreToolCallStatus } from '@google/gemini-cli-core';
import {
ACTIVE_SHELL_MAX_LINES,
COMPLETED_SHELL_MAX_LINES,
} from '../constants.js';

describe('toolLayoutUtils', () => {
describe('calculateToolContentMaxLines', () => {
interface CalculateToolContentMaxLinesTestCase {
desc: string;
options: Parameters<typeof calculateToolContentMaxLines>[0];
expected: number | undefined;
}

const testCases: CalculateToolContentMaxLinesTestCase[] = [
{
desc: 'returns undefined if availableTerminalHeight is undefined',
options: {
availableTerminalHeight: undefined,
isAlternateBuffer: false,
},
expected: undefined,
},
{
desc: 'returns maxLinesLimit if maxLinesLimit applies but availableTerminalHeight is undefined',
options: {
availableTerminalHeight: undefined,
isAlternateBuffer: false,
maxLinesLimit: 10,
},
expected: 10,
},
{
desc: 'returns available space directly in constrained terminal (Standard mode)',
options: {
availableTerminalHeight: 2,
isAlternateBuffer: false,
},
expected: 3,
},
{
desc: 'returns available space directly in constrained terminal (ASB mode)',
options: {
availableTerminalHeight: 4,
isAlternateBuffer: true,
},
expected: 3,
},
{
desc: 'returns remaining space if sufficient space exists (Standard mode)',
options: {
availableTerminalHeight: 20,
isAlternateBuffer: false,
},
expected: 17,
},
{
desc: 'returns remaining space if sufficient space exists (ASB mode)',
options: {
availableTerminalHeight: 20,
isAlternateBuffer: true,
},
expected: 13,
},
];

it.each(testCases)('$desc', ({ options, expected }) => {
const result = calculateToolContentMaxLines(options);
expect(result).toBe(expected);
});
});

describe('calculateShellMaxLines', () => {
interface CalculateShellMaxLinesTestCase {
desc: string;
options: Parameters<typeof calculateShellMaxLines>[0];
expected: number | undefined;
}

const testCases: CalculateShellMaxLinesTestCase[] = [
{
desc: 'returns undefined when not constrained and is expandable',
options: {
status: CoreToolCallStatus.Executing,
isAlternateBuffer: false,
isThisShellFocused: false,
availableTerminalHeight: 20,
constrainHeight: false,
isExpandable: true,
},
expected: undefined,
},
{
desc: 'returns ACTIVE_SHELL_MAX_LINES - SHELL_CONTENT_OVERHEAD for ASB mode when availableTerminalHeight is undefined',
options: {
status: CoreToolCallStatus.Executing,
isAlternateBuffer: true,
isThisShellFocused: false,
availableTerminalHeight: undefined,
constrainHeight: true,
isExpandable: false,
},
expected: ACTIVE_SHELL_MAX_LINES - SHELL_CONTENT_OVERHEAD,
},
{
desc: 'returns undefined for Standard mode when availableTerminalHeight is undefined',
options: {
status: CoreToolCallStatus.Executing,
isAlternateBuffer: false,
isThisShellFocused: false,
availableTerminalHeight: undefined,
constrainHeight: true,
isExpandable: false,
},
expected: undefined,
},
{
desc: 'handles small availableTerminalHeight gracefully without overflow in Standard mode',
options: {
status: CoreToolCallStatus.Executing,
isAlternateBuffer: false,
isThisShellFocused: false,
availableTerminalHeight: 2,
constrainHeight: true,
isExpandable: false,
},
expected: 1,
},
{
desc: 'handles small availableTerminalHeight gracefully without overflow in ASB mode',
options: {
status: CoreToolCallStatus.Executing,
isAlternateBuffer: true,
isThisShellFocused: false,
availableTerminalHeight: 6,
constrainHeight: true,
isExpandable: false,
},
expected: 4,
},
{
desc: 'handles negative availableTerminalHeight gracefully',
options: {
status: CoreToolCallStatus.Executing,
isAlternateBuffer: false,
isThisShellFocused: false,
availableTerminalHeight: -5,
constrainHeight: true,
isExpandable: false,
},
expected: 1,
},
{
desc: 'returns maxLinesBasedOnHeight for focused ASB shells',
options: {
status: CoreToolCallStatus.Executing,
isAlternateBuffer: true,
isThisShellFocused: true,
availableTerminalHeight: 30,
constrainHeight: false,
isExpandable: false,
},
expected: 28,
},
{
desc: 'falls back to COMPLETED_SHELL_MAX_LINES - SHELL_CONTENT_OVERHEAD for completed shells if space allows',
options: {
status: CoreToolCallStatus.Success,
isAlternateBuffer: false,
isThisShellFocused: false,
availableTerminalHeight: 100,
constrainHeight: true,
isExpandable: false,
},
expected: COMPLETED_SHELL_MAX_LINES - SHELL_CONTENT_OVERHEAD,
},
{
desc: 'falls back to ACTIVE_SHELL_MAX_LINES - SHELL_CONTENT_OVERHEAD for executing shells if space allows',
options: {
status: CoreToolCallStatus.Executing,
isAlternateBuffer: false,
isThisShellFocused: false,
availableTerminalHeight: 100,
constrainHeight: true,
isExpandable: false,
},
expected: ACTIVE_SHELL_MAX_LINES - SHELL_CONTENT_OVERHEAD,
},
];

it.each(testCases)('$desc', ({ options, expected }) => {
const result = calculateShellMaxLines(options);
expect(result).toBe(expected);
});
});
});
18 changes: 11 additions & 7 deletions packages/cli/src/ui/utils/toolLayoutUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,13 @@ export function calculateToolContentMaxLines(options: {
? TOOL_RESULT_ASB_RESERVED_LINE_COUNT
: TOOL_RESULT_STANDARD_RESERVED_LINE_COUNT;

let contentHeight = availableTerminalHeight
? Math.max(
availableTerminalHeight - TOOL_RESULT_STATIC_HEIGHT - reservedLines,
TOOL_RESULT_MIN_LINES_SHOWN + 1,
)
: undefined;
let contentHeight =
availableTerminalHeight !== undefined
? Math.max(
availableTerminalHeight - TOOL_RESULT_STATIC_HEIGHT - reservedLines,
TOOL_RESULT_MIN_LINES_SHOWN + 1,
)
: undefined;

if (maxLinesLimit !== undefined) {
contentHeight =
Expand Down Expand Up @@ -100,7 +101,10 @@ export function calculateShellMaxLines(options: {
: undefined;
}

const maxLinesBasedOnHeight = Math.max(1, availableTerminalHeight - 2);
const maxLinesBasedOnHeight = Math.max(
1,
availableTerminalHeight - TOOL_RESULT_STANDARD_RESERVED_LINE_COUNT,
);

// 3. Handle ASB mode focus expansion.
// We allow a focused shell in ASB mode to take up the full available height,
Expand Down
Loading