mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-20 11:00:40 -07:00
feat(core): migrate read_file to 1-based start_line/end_line parameters (#19526)
This commit is contained in:
@@ -930,7 +930,7 @@ describe('fileUtils', () => {
|
||||
expect(result.returnDisplay).toContain('Path is a directory');
|
||||
});
|
||||
|
||||
it('should paginate text files correctly (offset and limit)', async () => {
|
||||
it('should paginate text files correctly (startLine and endLine)', async () => {
|
||||
const lines = Array.from({ length: 20 }, (_, i) => `Line ${i + 1}`);
|
||||
actualNodeFs.writeFileSync(testTextFilePath, lines.join('\n'));
|
||||
|
||||
@@ -938,9 +938,9 @@ describe('fileUtils', () => {
|
||||
testTextFilePath,
|
||||
tempRootDir,
|
||||
new StandardFileSystemService(),
|
||||
5,
|
||||
5,
|
||||
); // Read lines 6-10
|
||||
6,
|
||||
10,
|
||||
); // Read lines 6-10 (1-based)
|
||||
const expectedContent = lines.slice(5, 10).join('\n');
|
||||
|
||||
expect(result.llmContent).toBe(expectedContent);
|
||||
@@ -954,13 +954,13 @@ describe('fileUtils', () => {
|
||||
const lines = Array.from({ length: 20 }, (_, i) => `Line ${i + 1}`);
|
||||
actualNodeFs.writeFileSync(testTextFilePath, lines.join('\n'));
|
||||
|
||||
// Read from line 11 to 20. The start is not 0, so it's truncated.
|
||||
// Read from line 11 to 20. The start is not 1, so it's truncated.
|
||||
const result = await processSingleFileContent(
|
||||
testTextFilePath,
|
||||
tempRootDir,
|
||||
new StandardFileSystemService(),
|
||||
10,
|
||||
10,
|
||||
11,
|
||||
20,
|
||||
);
|
||||
const expectedContent = lines.slice(10, 20).join('\n');
|
||||
|
||||
@@ -971,7 +971,7 @@ describe('fileUtils', () => {
|
||||
expect(result.linesShown).toEqual([11, 20]);
|
||||
});
|
||||
|
||||
it('should handle limit exceeding file length', async () => {
|
||||
it('should handle endLine exceeding file length', async () => {
|
||||
const lines = ['Line 1', 'Line 2'];
|
||||
actualNodeFs.writeFileSync(testTextFilePath, lines.join('\n'));
|
||||
|
||||
@@ -979,7 +979,7 @@ describe('fileUtils', () => {
|
||||
testTextFilePath,
|
||||
tempRootDir,
|
||||
new StandardFileSystemService(),
|
||||
0,
|
||||
1,
|
||||
10,
|
||||
);
|
||||
const expectedContent = lines.join('\n');
|
||||
@@ -1015,21 +1015,22 @@ describe('fileUtils', () => {
|
||||
expect(result.isTruncated).toBe(true);
|
||||
});
|
||||
|
||||
it('should truncate when line count exceeds the limit', async () => {
|
||||
const lines = Array.from({ length: 11 }, (_, i) => `Line ${i + 1}`);
|
||||
it('should truncate when line count exceeds the default limit', async () => {
|
||||
const lines = Array.from({ length: 2500 }, (_, i) => `Line ${i + 1}`);
|
||||
actualNodeFs.writeFileSync(testTextFilePath, lines.join('\n'));
|
||||
|
||||
// Read 5 lines, but there are 11 total
|
||||
// No ranges provided, should use default limit (2000)
|
||||
const result = await processSingleFileContent(
|
||||
testTextFilePath,
|
||||
tempRootDir,
|
||||
new StandardFileSystemService(),
|
||||
0,
|
||||
5,
|
||||
);
|
||||
|
||||
expect(result.isTruncated).toBe(true);
|
||||
expect(result.returnDisplay).toBe('Read lines 1-5 of 11 from test.txt');
|
||||
expect(result.returnDisplay).toBe(
|
||||
'Read lines 1-2000 of 2500 from test.txt',
|
||||
);
|
||||
expect(result.linesShown).toEqual([1, 2000]);
|
||||
});
|
||||
|
||||
it('should truncate when a line length exceeds the character limit', async () => {
|
||||
@@ -1043,7 +1044,7 @@ describe('fileUtils', () => {
|
||||
testTextFilePath,
|
||||
tempRootDir,
|
||||
new StandardFileSystemService(),
|
||||
0,
|
||||
1,
|
||||
11,
|
||||
);
|
||||
|
||||
@@ -1069,7 +1070,7 @@ describe('fileUtils', () => {
|
||||
testTextFilePath,
|
||||
tempRootDir,
|
||||
new StandardFileSystemService(),
|
||||
0,
|
||||
1,
|
||||
10,
|
||||
);
|
||||
expect(result.isTruncated).toBe(true);
|
||||
|
||||
@@ -53,7 +53,7 @@ export async function loadWasmBinary(
|
||||
}
|
||||
|
||||
// Constants for text file processing
|
||||
const DEFAULT_MAX_LINES_TEXT_FILE = 2000;
|
||||
export const DEFAULT_MAX_LINES_TEXT_FILE = 2000;
|
||||
const MAX_LINE_LENGTH_TEXT_FILE = 2000;
|
||||
|
||||
// Default values for encoding and separator format
|
||||
@@ -399,16 +399,17 @@ export interface ProcessedFileReadResult {
|
||||
* Reads and processes a single file, handling text, images, and PDFs.
|
||||
* @param filePath Absolute path to the file.
|
||||
* @param rootDirectory Absolute path to the project root for relative path display.
|
||||
* @param offset Optional offset for text files (0-based line number).
|
||||
* @param limit Optional limit for text files (number of lines to read).
|
||||
* @param _fileSystemService Currently unused in this function; kept for signature stability.
|
||||
* @param startLine Optional 1-based line number to start reading from.
|
||||
* @param endLine Optional 1-based line number to end reading at (inclusive).
|
||||
* @returns ProcessedFileReadResult object.
|
||||
*/
|
||||
export async function processSingleFileContent(
|
||||
filePath: string,
|
||||
rootDirectory: string,
|
||||
fileSystemService: FileSystemService,
|
||||
offset?: number,
|
||||
limit?: number,
|
||||
_fileSystemService: FileSystemService,
|
||||
startLine?: number,
|
||||
endLine?: number,
|
||||
): Promise<ProcessedFileReadResult> {
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
@@ -474,14 +475,24 @@ export async function processSingleFileContent(
|
||||
const lines = content.split('\n');
|
||||
const originalLineCount = lines.length;
|
||||
|
||||
const startLine = offset || 0;
|
||||
const effectiveLimit =
|
||||
limit === undefined ? DEFAULT_MAX_LINES_TEXT_FILE : limit;
|
||||
// Ensure endLine does not exceed originalLineCount
|
||||
const endLine = Math.min(startLine + effectiveLimit, originalLineCount);
|
||||
// Ensure selectedLines doesn't try to slice beyond array bounds if startLine is too high
|
||||
const actualStartLine = Math.min(startLine, originalLineCount);
|
||||
const selectedLines = lines.slice(actualStartLine, endLine);
|
||||
let sliceStart = 0;
|
||||
let sliceEnd = originalLineCount;
|
||||
|
||||
if (startLine !== undefined || endLine !== undefined) {
|
||||
sliceStart = startLine ? startLine - 1 : 0;
|
||||
sliceEnd = endLine
|
||||
? Math.min(endLine, originalLineCount)
|
||||
: Math.min(
|
||||
sliceStart + DEFAULT_MAX_LINES_TEXT_FILE,
|
||||
originalLineCount,
|
||||
);
|
||||
} else {
|
||||
sliceEnd = Math.min(DEFAULT_MAX_LINES_TEXT_FILE, originalLineCount);
|
||||
}
|
||||
|
||||
// Ensure selectedLines doesn't try to slice beyond array bounds
|
||||
const actualStart = Math.min(sliceStart, originalLineCount);
|
||||
const selectedLines = lines.slice(actualStart, sliceEnd);
|
||||
|
||||
let linesWereTruncatedInLength = false;
|
||||
const formattedLines = selectedLines.map((line) => {
|
||||
@@ -494,17 +505,18 @@ export async function processSingleFileContent(
|
||||
return line;
|
||||
});
|
||||
|
||||
const contentRangeTruncated =
|
||||
startLine > 0 || endLine < originalLineCount;
|
||||
const isTruncated = contentRangeTruncated || linesWereTruncatedInLength;
|
||||
const isTruncated =
|
||||
actualStart > 0 ||
|
||||
sliceEnd < originalLineCount ||
|
||||
linesWereTruncatedInLength;
|
||||
const llmContent = formattedLines.join('\n');
|
||||
|
||||
// By default, return nothing to streamline the common case of a successful read_file.
|
||||
let returnDisplay = '';
|
||||
if (contentRangeTruncated) {
|
||||
if (actualStart > 0 || sliceEnd < originalLineCount) {
|
||||
returnDisplay = `Read lines ${
|
||||
actualStartLine + 1
|
||||
}-${endLine} of ${originalLineCount} from ${relativePathForDisplay}`;
|
||||
actualStart + 1
|
||||
}-${sliceEnd} of ${originalLineCount} from ${relativePathForDisplay}`;
|
||||
if (linesWereTruncatedInLength) {
|
||||
returnDisplay += ' (some lines were shortened)';
|
||||
}
|
||||
@@ -517,7 +529,7 @@ export async function processSingleFileContent(
|
||||
returnDisplay,
|
||||
isTruncated,
|
||||
originalLineCount,
|
||||
linesShown: [actualStartLine + 1, endLine],
|
||||
linesShown: [actualStart + 1, sliceEnd],
|
||||
};
|
||||
}
|
||||
case 'image':
|
||||
|
||||
Reference in New Issue
Block a user