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
Original file line number Diff line number Diff line change
Expand Up @@ -357,9 +357,8 @@ describe('DenseToolMessage', () => {
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('→ Found 2 matches');
// Matches are rendered in a secondary list for high-signal summaries
expect(output).toContain('file1.ts:10: match 1');
expect(output).toContain('file2.ts:20: match 2');
// Matches should no longer be rendered in dense mode to keep it compact
expect(output).not.toContain('file1.ts:10: match 1');
expect(output).toMatchSnapshot();
});

Expand Down Expand Up @@ -400,9 +399,8 @@ describe('DenseToolMessage', () => {
const output = lastFrame();
expect(output).toContain('Attempting to read files from **/*.ts');
expect(output).toContain('→ Read 3 file(s) (1 ignored)');
expect(output).toContain('file1.ts');
expect(output).toContain('file2.ts');
expect(output).toContain('file3.ts');
// File lists should no longer be rendered in dense mode
expect(output).not.toContain('file1.ts');
expect(output).toMatchSnapshot();
});

Expand Down Expand Up @@ -477,6 +475,28 @@ describe('DenseToolMessage', () => {
expect(output).toMatchSnapshot();
});

it('truncates long description but preserves tool name (< 25 chars)', async () => {
const longDescription =
'This is a very long description that should definitely be truncated because it exceeds the available terminal width and we want to see how it behaves.';
const toolName = 'tool-name-is-24-chars-!!'; // Exactly 24 chars
const { lastFrame, waitUntilReady } = await renderWithProviders(
<DenseToolMessage
{...defaultProps}
name={toolName}
description={longDescription}
terminalWidth={50} // Narrow width to force truncation
/>,
);
await waitUntilReady();
const output = lastFrame();

// Tool name should be fully present (it plus one space is exactly 25, fitting the maxWidth)
expect(output).toContain(toolName);
// Description should be present but truncated
expect(output).toContain('This is a');
expect(output).toMatchSnapshot();
});

describe('Toggleable Diff View (Alternate Buffer)', () => {
const diffResult: FileDiff = {
fileDiff: '@@ -1,1 +1,1 @@\n-old line\n+new line',
Expand Down
71 changes: 18 additions & 53 deletions packages/cli/src/ui/components/messages/DenseToolMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,27 +72,6 @@ const hasPayload = (res: unknown): res is PayloadResult => {
return typeof value === 'string';
};

const RenderItemsList: React.FC<{
items?: string[];
maxVisible?: number;
}> = ({ items, maxVisible = 20 }) => {
if (!items || items.length === 0) return null;
return (
<Box flexDirection="column">
{items.slice(0, maxVisible).map((item, i) => (
<Text key={i} color={theme.text.secondary}>
{item}
</Text>
))}
{items.length > maxVisible && (
<Text color={theme.text.secondary}>
... and {items.length - maxVisible} more
</Text>
)}
</Box>
);
};

function getFileOpData(
diff: FileDiff,
status: CoreToolCallStatus,
Expand Down Expand Up @@ -188,8 +167,6 @@ function getFileOpData(
}

function getReadManyFilesData(result: ReadManyFilesResult): ViewParts {
const items = result.files ?? [];
const maxVisible = 10;
const includePatterns = result.include?.join(', ') ?? '';
const description = (
<Text color={theme.text.secondary} wrap="truncate-end">
Expand All @@ -198,18 +175,12 @@ function getReadManyFilesData(result: ReadManyFilesResult): ViewParts {
);

const skippedCount = result.skipped?.length ?? 0;
const summaryStr = `Read ${items.length} file(s)${
const summaryStr = `Read ${result.files.length} file(s)${
skippedCount > 0 ? ` (${skippedCount} ignored)` : ''
}`;
const summary = <Text color={theme.text.accent}>→ {summaryStr}</Text>;
const hasItems = items.length > 0;
const payload = hasItems ? (
<Box flexDirection="column" marginLeft={2}>
{hasItems && <RenderItemsList items={items} maxVisible={maxVisible} />}
</Box>
) : undefined;

return { description, summary, payload };
return { description, summary, payload: undefined };
}

function getListDirectoryData(
Expand Down Expand Up @@ -258,20 +229,11 @@ function getGenericSuccessData(
</Text>
);
} else if (isGrepResult(resultDisplay)) {
summary = <Text color={theme.text.accent}>→ {resultDisplay.summary}</Text>;
const matches = resultDisplay.matches;
if (matches.length > 0) {
payload = (
<Box flexDirection="column" marginLeft={2}>
<RenderItemsList
items={matches.map(
(m) => `${m.filePath}:${m.lineNumber}: ${m.line.trim()}`,
)}
maxVisible={10}
/>
</Box>
);
}
summary = (
<Text color={theme.text.accent} wrap="truncate-end">
→ {resultDisplay.summary}
</Text>
);
} else if (isTodoList(resultDisplay)) {
summary = (
<Text color={theme.text.accent} wrap="wrap">
Expand Down Expand Up @@ -488,15 +450,18 @@ export const DenseToolMessage: React.FC<DenseToolMessageProps> = (props) => {
return (
<Box flexDirection="column">
<Box marginLeft={2} flexDirection="row" flexWrap="wrap">
<ToolStatusIndicator status={status} name={name} />
<Box maxWidth={25} flexShrink={1} flexGrow={0}>
<Text color={theme.text.primary} bold wrap="truncate-end">
{name}{' '}
</Text>
</Box>
<Box marginLeft={1} flexShrink={1} flexGrow={0}>
{description}
<Box flexDirection="row" flexShrink={1}>
<ToolStatusIndicator status={status} name={name} />
<Box maxWidth={25} flexShrink={0} flexGrow={0}>
<Text color={theme.text.primary} bold wrap="truncate-end">
{name}{' '}
</Text>
</Box>
<Box marginLeft={1} flexShrink={1} flexGrow={0}>
{description}
</Box>
</Box>

{summary && (
<Box
key="tool-summary"
Expand Down
11 changes: 0 additions & 11 deletions packages/cli/src/ui/components/messages/ToolGroupMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ import {
WRITE_FILE_DISPLAY_NAME,
READ_MANY_FILES_DISPLAY_NAME,
isFileDiff,
isGrepResult,
isListResult,
} from '@google/gemini-cli-core';
import { useUIState } from '../../contexts/UIStateContext.js';
import { getToolGroupBorderAppearance } from '../../utils/borderStyles.js';
Expand Down Expand Up @@ -81,15 +79,6 @@ export const hasDensePayload = (tool: IndividualToolCallDisplay): boolean => {
// TODO(24053): Usage of type guards makes this class too aware of internals
if (isFileDiff(res)) return true;
if (tool.confirmationDetails?.type === 'edit') return true;
if (isGrepResult(res) && res.matches.length > 0) return true;

// ReadManyFilesResult check (has 'include' and 'files')
if (isListResult(res) && 'include' in res) {
const includeProp = (res as { include?: unknown }).include;
if (Array.isArray(includeProp) && res.files.length > 0) {
return true;
}
}

// Generic summary/payload pattern
if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ exports[`DenseToolMessage > renders correctly for Errored Edit tool 1`] = `

exports[`DenseToolMessage > renders correctly for ReadManyFiles results 1`] = `
" ✓ test-tool Attempting to read files from **/*.ts → Read 3 file(s) (1 ignored)

file1.ts
file2.ts
file3.ts
"
`;

Expand Down Expand Up @@ -110,9 +106,6 @@ exports[`DenseToolMessage > renders correctly for file diff results with stats 1

exports[`DenseToolMessage > renders correctly for grep results 1`] = `
" ✓ test-tool Test description → Found 2 matches

file1.ts:10: match 1
file2.ts:20: match 2
"
`;

Expand All @@ -136,6 +129,12 @@ exports[`DenseToolMessage > renders generic output message for unknown object re
"
`;

exports[`DenseToolMessage > truncates long description but preserves tool name (< 25 chars) 1`] = `
" ✓ tool-name-is-24-chars-!! This is a very long description that should definitely be truncated …
→ Success result
"
`;

exports[`DenseToolMessage > truncates long string results 1`] = `
" ✓ test-tool Test description
→ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…
Expand Down
14 changes: 7 additions & 7 deletions packages/core/src/tools/ls.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ describe('LSTool', () => {
expect(result.llmContent).toContain('[DIR] subdir');
expect(result.llmContent).toContain('file1.txt');
expect(result.returnDisplay).toEqual({
summary: 'Listed 2 item(s).',
summary: 'Found 2 item(s).',
files: ['[DIR] subdir', 'file1.txt'],
});
});
Expand All @@ -150,7 +150,7 @@ describe('LSTool', () => {

expect(result.llmContent).toContain('secondary-file.txt');
expect(result.returnDisplay).toEqual({
summary: 'Listed 1 item(s).',
summary: 'Found 1 item(s).',
files: expect.any(Array),
});
});
Expand Down Expand Up @@ -178,7 +178,7 @@ describe('LSTool', () => {
expect(result.llmContent).toContain('file1.txt');
expect(result.llmContent).not.toContain('file2.log');
expect(result.returnDisplay).toEqual({
summary: 'Listed 1 item(s).',
summary: 'Found 1 item(s).',
files: expect.any(Array),
});
});
Expand All @@ -195,7 +195,7 @@ describe('LSTool', () => {
expect(result.llmContent).not.toContain('file2.log');
// .git is always ignored by default.
expect(result.returnDisplay).toEqual(
expect.objectContaining({ summary: 'Listed 2 item(s). (2 ignored)' }),
expect.objectContaining({ summary: 'Found 2 item(s). (2 ignored)' }),
);
});

Expand All @@ -212,7 +212,7 @@ describe('LSTool', () => {
expect(result.llmContent).toContain('file1.txt');
expect(result.llmContent).not.toContain('file2.log');
expect(result.returnDisplay).toEqual(
expect.objectContaining({ summary: 'Listed 2 item(s). (1 ignored)' }),
expect.objectContaining({ summary: 'Found 2 item(s). (1 ignored)' }),
);
});

Expand Down Expand Up @@ -301,7 +301,7 @@ describe('LSTool', () => {
expect(result.llmContent).toContain('file1.txt');
expect(result.llmContent).not.toContain('problematic.txt');
expect(result.returnDisplay).toEqual({
summary: 'Listed 1 item(s).',
summary: 'Found 1 item(s).',
files: expect.any(Array),
});

Expand Down Expand Up @@ -364,7 +364,7 @@ describe('LSTool', () => {

expect(result.llmContent).toContain('secondary-file.txt');
expect(result.returnDisplay).toEqual({
summary: 'Listed 1 item(s).',
summary: 'Found 1 item(s).',
files: expect.any(Array),
});
});
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/tools/ls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ class LSToolInvocation extends BaseToolInvocation<LSToolParams, ToolResult> {
resultMessage = appendJitContext(resultMessage, jitContext);
}

let displayMessage = `Listed ${entries.length} item(s).`;
let displayMessage = `Found ${entries.length} item(s).`;
if (ignoredCount > 0) {
displayMessage += ` (${ignoredCount} ignored)`;
}
Expand Down
Loading