Read and write files through Zed (#6169)

Co-authored-by: Agus Zubiaga <agus@zed.dev>
This commit is contained in:
Conrad Irwin
2025-08-18 16:29:45 -06:00
committed by GitHub
parent 4394b6ab4f
commit fb3ceb0da4
17 changed files with 268 additions and 50 deletions

View File

@@ -26,6 +26,7 @@ import {
detectFileType,
processSingleFileContent,
} from './fileUtils.js';
import { StandardFileSystemService } from '../services/fileSystemService.js';
vi.mock('mime-types', () => ({
default: { lookup: vi.fn() },
@@ -280,6 +281,7 @@ describe('fileUtils', () => {
const result = await processSingleFileContent(
testTextFilePath,
tempRootDir,
new StandardFileSystemService(),
);
expect(result.llmContent).toBe(content);
expect(result.returnDisplay).toBe('');
@@ -290,6 +292,7 @@ describe('fileUtils', () => {
const result = await processSingleFileContent(
nonexistentFilePath,
tempRootDir,
new StandardFileSystemService(),
);
expect(result.error).toContain('File not found');
expect(result.returnDisplay).toContain('File not found');
@@ -303,6 +306,7 @@ describe('fileUtils', () => {
const result = await processSingleFileContent(
testTextFilePath,
tempRootDir,
new StandardFileSystemService(),
);
expect(result.error).toContain('Simulated read error');
expect(result.returnDisplay).toContain('Simulated read error');
@@ -317,6 +321,7 @@ describe('fileUtils', () => {
const result = await processSingleFileContent(
testImageFilePath,
tempRootDir,
new StandardFileSystemService(),
);
expect(result.error).toContain('Simulated image read error');
expect(result.returnDisplay).toContain('Simulated image read error');
@@ -329,6 +334,7 @@ describe('fileUtils', () => {
const result = await processSingleFileContent(
testImageFilePath,
tempRootDir,
new StandardFileSystemService(),
);
expect(
(result.llmContent as { inlineData: unknown }).inlineData,
@@ -350,6 +356,7 @@ describe('fileUtils', () => {
const result = await processSingleFileContent(
testPdfFilePath,
tempRootDir,
new StandardFileSystemService(),
);
expect(
(result.llmContent as { inlineData: unknown }).inlineData,
@@ -378,6 +385,7 @@ describe('fileUtils', () => {
const result = await processSingleFileContent(
testSvgFilePath,
tempRootDir,
new StandardFileSystemService(),
);
expect(result.llmContent).toBe(svgContent);
@@ -395,6 +403,7 @@ describe('fileUtils', () => {
const result = await processSingleFileContent(
testBinaryFilePath,
tempRootDir,
new StandardFileSystemService(),
);
expect(result.llmContent).toContain(
'Cannot display content of binary file',
@@ -403,7 +412,11 @@ describe('fileUtils', () => {
});
it('should handle path being a directory', async () => {
const result = await processSingleFileContent(directoryPath, tempRootDir);
const result = await processSingleFileContent(
directoryPath,
tempRootDir,
new StandardFileSystemService(),
);
expect(result.error).toContain('Path is a directory');
expect(result.returnDisplay).toContain('Path is a directory');
});
@@ -415,6 +428,7 @@ describe('fileUtils', () => {
const result = await processSingleFileContent(
testTextFilePath,
tempRootDir,
new StandardFileSystemService(),
5,
5,
); // Read lines 6-10
@@ -435,6 +449,7 @@ describe('fileUtils', () => {
const result = await processSingleFileContent(
testTextFilePath,
tempRootDir,
new StandardFileSystemService(),
10,
10,
);
@@ -454,6 +469,7 @@ describe('fileUtils', () => {
const result = await processSingleFileContent(
testTextFilePath,
tempRootDir,
new StandardFileSystemService(),
0,
10,
);
@@ -476,6 +492,7 @@ describe('fileUtils', () => {
const result = await processSingleFileContent(
testTextFilePath,
tempRootDir,
new StandardFileSystemService(),
);
expect(result.llmContent).toContain('Short line');
@@ -497,6 +514,7 @@ describe('fileUtils', () => {
const result = await processSingleFileContent(
testTextFilePath,
tempRootDir,
new StandardFileSystemService(),
0,
5,
);
@@ -515,6 +533,7 @@ describe('fileUtils', () => {
const result = await processSingleFileContent(
testTextFilePath,
tempRootDir,
new StandardFileSystemService(),
0,
11,
);
@@ -540,6 +559,7 @@ describe('fileUtils', () => {
const result = await processSingleFileContent(
testTextFilePath,
tempRootDir,
new StandardFileSystemService(),
0,
10,
);
@@ -558,6 +578,7 @@ describe('fileUtils', () => {
const result = await processSingleFileContent(
testTextFilePath,
tempRootDir,
new StandardFileSystemService(),
);
expect(result.error).toContain('File size exceeds the 20MB limit');

View File

@@ -8,6 +8,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { PartUnion } from '@google/genai';
import mime from 'mime-types';
import { FileSystemService } from '../services/fileSystemService.js';
// Constants for text file processing
const DEFAULT_MAX_LINES_TEXT_FILE = 2000;
@@ -223,6 +224,7 @@ export interface ProcessedFileReadResult {
export async function processSingleFileContent(
filePath: string,
rootDirectory: string,
fileSystemService: FileSystemService,
offset?: number,
limit?: number,
): Promise<ProcessedFileReadResult> {
@@ -279,14 +281,14 @@ export async function processSingleFileContent(
returnDisplay: `Skipped large SVG file (>1MB): ${relativePathForDisplay}`,
};
}
const content = await fs.promises.readFile(filePath, 'utf8');
const content = await fileSystemService.readTextFile(filePath);
return {
llmContent: content,
returnDisplay: `Read SVG as text: ${relativePathForDisplay}`,
};
}
case 'text': {
const content = await fs.promises.readFile(filePath, 'utf8');
const content = await fileSystemService.readTextFile(filePath);
const lines = content.split('\n');
const originalLineCount = lines.length;