文件系统 API
概述
Trae 文件系统 API 提供了完整的文件和目录操作功能,支持本地文件系统、远程文件系统和虚拟文件系统。开发者可以通过 API 进行文件读写、目录管理、文件监控等操作。
核心概念
URI 和路径
typescript
import { Uri } from '@trae/api';
// 创建文件 URI
const fileUri = Uri.file('/path/to/file.txt');
const httpUri = Uri.parse('https://example.com/file.txt');
const customUri = Uri.parse('custom://scheme/path');
// URI 属性
console.log(fileUri.scheme); // 'file'
console.log(fileUri.path); // '/path/to/file.txt'
console.log(fileUri.fsPath); // 平台特定的文件系统路径文件类型
typescript
enum FileType {
Unknown = 0,
File = 1,
Directory = 2,
SymbolicLink = 64
}
interface FileStat {
type: FileType;
ctime: number; // 创建时间
mtime: number; // 修改时间
size: number; // 文件大小
}API 参考
文件读写操作
readFile()
读取文件内容:
typescript
import { workspace } from '@trae/api';
// 读取文本文件
const readTextFile = async (uri: Uri): Promise<string> => {
try {
const content = await workspace.fs.readFile(uri);
return Buffer.from(content).toString('utf8');
} catch (error) {
console.error('Failed to read file:', error);
throw error;
}
};
// 读取二进制文件
const readBinaryFile = async (uri: Uri): Promise<Uint8Array> => {
return await workspace.fs.readFile(uri);
};
// 示例使用
const fileUri = Uri.file('/path/to/file.txt');
const content = await readTextFile(fileUri);
console.log('File content:', content);writeFile()
写入文件内容:
typescript
// 写入文本文件
const writeTextFile = async (uri: Uri, content: string): Promise<void> => {
try {
const buffer = Buffer.from(content, 'utf8');
await workspace.fs.writeFile(uri, buffer);
} catch (error) {
console.error('Failed to write file:', error);
throw error;
}
};
// 写入二进制文件
const writeBinaryFile = async (uri: Uri, content: Uint8Array): Promise<void> => {
await workspace.fs.writeFile(uri, content);
};
// 示例使用
const newFileUri = Uri.file('/path/to/new-file.txt');
await writeTextFile(newFileUri, 'Hello, World!');目录操作
readDirectory()
读取目录内容:
typescript
// 读取目录
const readDirectory = async (uri: Uri): Promise<[string, FileType][]> => {
try {
return await workspace.fs.readDirectory(uri);
} catch (error) {
console.error('Failed to read directory:', error);
return [];
}
};
// 递归读取目录
const readDirectoryRecursive = async (uri: Uri): Promise<Uri[]> => {
const files: Uri[] = [];
const entries = await workspace.fs.readDirectory(uri);
for (const [name, type] of entries) {
const childUri = Uri.joinPath(uri, name);
if (type === FileType.File) {
files.push(childUri);
} else if (type === FileType.Directory) {
const childFiles = await readDirectoryRecursive(childUri);
files.push(...childFiles);
}
}
return files;
};
// 示例使用
const dirUri = Uri.file('/path/to/directory');
const entries = await readDirectory(dirUri);
console.log('Directory entries:', entries);createDirectory()
创建目录:
typescript
// 创建单个目录
const createDirectory = async (uri: Uri): Promise<void> => {
try {
await workspace.fs.createDirectory(uri);
} catch (error) {
console.error('Failed to create directory:', error);
throw error;
}
};
// 创建嵌套目录
const createDirectoryRecursive = async (uri: Uri): Promise<void> => {
const parts = uri.path.split('/').filter(part => part.length > 0);
let currentPath = uri.scheme === 'file' ? '/' : '';
for (const part of parts) {
currentPath = currentPath.endsWith('/') ? currentPath + part : currentPath + '/' + part;
const currentUri = Uri.file(currentPath);
try {
await workspace.fs.stat(currentUri);
} catch {
await workspace.fs.createDirectory(currentUri);
}
}
};文件和目录管理
stat()
获取文件或目录信息:
typescript
// 获取文件统计信息
const getFileStat = async (uri: Uri): Promise<FileStat | null> => {
try {
return await workspace.fs.stat(uri);
} catch (error) {
console.error('Failed to get file stat:', error);
return null;
}
};
// 检查文件是否存在
const fileExists = async (uri: Uri): Promise<boolean> => {
try {
await workspace.fs.stat(uri);
return true;
} catch {
return false;
}
};
// 检查是否为目录
const isDirectory = async (uri: Uri): Promise<boolean> => {
try {
const stat = await workspace.fs.stat(uri);
return stat.type === FileType.Directory;
} catch {
return false;
}
};delete()
删除文件或目录:
typescript
// 删除文件
const deleteFile = async (uri: Uri): Promise<void> => {
try {
await workspace.fs.delete(uri, { recursive: false, useTrash: false });
} catch (error) {
console.error('Failed to delete file:', error);
throw error;
}
};
// 删除目录(递归)
const deleteDirectory = async (uri: Uri, useTrash = true): Promise<void> => {
try {
await workspace.fs.delete(uri, { recursive: true, useTrash });
} catch (error) {
console.error('Failed to delete directory:', error);
throw error;
}
};rename()
重命名或移动文件:
typescript
// 重命名文件
const renameFile = async (oldUri: Uri, newUri: Uri): Promise<void> => {
try {
await workspace.fs.rename(oldUri, newUri, { overwrite: false });
} catch (error) {
console.error('Failed to rename file:', error);
throw error;
}
};
// 移动文件到新目录
const moveFile = async (sourceUri: Uri, targetDirUri: Uri): Promise<Uri> => {
const fileName = sourceUri.path.split('/').pop() || 'unknown';
const targetUri = Uri.joinPath(targetDirUri, fileName);
await workspace.fs.rename(sourceUri, targetUri, { overwrite: false });
return targetUri;
};文件复制
typescript
// 复制文件
const copyFile = async (sourceUri: Uri, targetUri: Uri): Promise<void> => {
try {
await workspace.fs.copy(sourceUri, targetUri, { overwrite: false });
} catch (error) {
console.error('Failed to copy file:', error);
throw error;
}
};
// 复制目录
const copyDirectory = async (sourceUri: Uri, targetUri: Uri): Promise<void> => {
// 创建目标目录
await workspace.fs.createDirectory(targetUri);
// 读取源目录内容
const entries = await workspace.fs.readDirectory(sourceUri);
for (const [name, type] of entries) {
const sourceChild = Uri.joinPath(sourceUri, name);
const targetChild = Uri.joinPath(targetUri, name);
if (type === FileType.File) {
await copyFile(sourceChild, targetChild);
} else if (type === FileType.Directory) {
await copyDirectory(sourceChild, targetChild);
}
}
};文件监控
文件系统监控器
typescript
import { workspace, FileSystemWatcher } from '@trae/api';
// 创建文件监控器
const createFileWatcher = (pattern: string): FileSystemWatcher => {
const watcher = workspace.createFileSystemWatcher(pattern);
// 监听文件创建
watcher.onDidCreate(uri => {
console.log('File created:', uri.fsPath);
handleFileCreated(uri);
});
// 监听文件修改
watcher.onDidChange(uri => {
console.log('File changed:', uri.fsPath);
handleFileChanged(uri);
});
// 监听文件删除
watcher.onDidDelete(uri => {
console.log('File deleted:', uri.fsPath);
handleFileDeleted(uri);
});
return watcher;
};
// 示例:监控特定类型的文件
const jsWatcher = createFileWatcher('**/*.js');
const tsWatcher = createFileWatcher('**/*.{ts,tsx}');
const configWatcher = createFileWatcher('**/package.json');高级文件监控
typescript
class FileMonitor {
private watchers: Map<string, FileSystemWatcher> = new Map();
private callbacks: Map<string, Function[]> = new Map();
// 添加监控
watch(pattern: string, callback: (uri: Uri, event: string) => void): void {
if (!this.watchers.has(pattern)) {
const watcher = workspace.createFileSystemWatcher(pattern);
watcher.onDidCreate(uri => this.notifyCallbacks(pattern, uri, 'create'));
watcher.onDidChange(uri => this.notifyCallbacks(pattern, uri, 'change'));
watcher.onDidDelete(uri => this.notifyCallbacks(pattern, uri, 'delete'));
this.watchers.set(pattern, watcher);
this.callbacks.set(pattern, []);
}
this.callbacks.get(pattern)?.push(callback);
}
// 移除监控
unwatch(pattern: string): void {
const watcher = this.watchers.get(pattern);
if (watcher) {
watcher.dispose();
this.watchers.delete(pattern);
this.callbacks.delete(pattern);
}
}
private notifyCallbacks(pattern: string, uri: Uri, event: string): void {
const callbacks = this.callbacks.get(pattern) || [];
callbacks.forEach(callback => callback(uri, event));
}
// 清理所有监控器
dispose(): void {
this.watchers.forEach(watcher => watcher.dispose());
this.watchers.clear();
this.callbacks.clear();
}
}文件搜索
基本搜索
typescript
// 搜索文件
const searchFiles = async (pattern: string, exclude?: string): Promise<Uri[]> => {
return await workspace.findFiles(pattern, exclude);
};
// 示例搜索
const jsFiles = await searchFiles('**/*.js', '**/node_modules/**');
const configFiles = await searchFiles('**/config.{json,js,ts}');
const testFiles = await searchFiles('**/*.test.{js,ts}');高级搜索
typescript
class FileSearcher {
// 按内容搜索文件
async searchByContent(pattern: RegExp, filePattern = '**/*'): Promise<{ uri: Uri; matches: RegExpMatchArray[] }[]> {
const files = await workspace.findFiles(filePattern);
const results = [];
for (const file of files) {
try {
const content = await workspace.fs.readFile(file);
const text = Buffer.from(content).toString('utf8');
const matches = Array.from(text.matchAll(pattern));
if (matches.length > 0) {
results.push({ uri: file, matches });
}
} catch (error) {
console.warn('Failed to read file for search:', file.fsPath);
}
}
return results;
}
// 按文件大小搜索
async searchBySize(minSize: number, maxSize: number): Promise<{ uri: Uri; size: number }[]> {
const files = await workspace.findFiles('**/*');
const results = [];
for (const file of files) {
try {
const stat = await workspace.fs.stat(file);
if (stat.size >= minSize && stat.size <= maxSize) {
results.push({ uri: file, size: stat.size });
}
} catch (error) {
console.warn('Failed to get file stat:', file.fsPath);
}
}
return results;
}
// 按修改时间搜索
async searchByModificationTime(since: Date): Promise<{ uri: Uri; mtime: number }[]> {
const files = await workspace.findFiles('**/*');
const results = [];
const sinceTime = since.getTime();
for (const file of files) {
try {
const stat = await workspace.fs.stat(file);
if (stat.mtime >= sinceTime) {
results.push({ uri: file, mtime: stat.mtime });
}
} catch (error) {
console.warn('Failed to get file stat:', file.fsPath);
}
}
return results;
}
}文件操作工具
批量操作
typescript
class BatchFileOperations {
// 批量复制文件
async copyFiles(operations: { source: Uri; target: Uri }[]): Promise<void> {
const promises = operations.map(op =>
workspace.fs.copy(op.source, op.target, { overwrite: false })
);
await Promise.all(promises);
}
// 批量删除文件
async deleteFiles(uris: Uri[], useTrash = true): Promise<void> {
const promises = uris.map(uri =>
workspace.fs.delete(uri, { recursive: false, useTrash })
);
await Promise.all(promises);
}
// 批量重命名文件
async renameFiles(operations: { oldUri: Uri; newUri: Uri }[]): Promise<void> {
for (const op of operations) {
await workspace.fs.rename(op.oldUri, op.newUri, { overwrite: false });
}
}
}文件同步
typescript
class FileSynchronizer {
// 同步两个目录
async syncDirectories(sourceUri: Uri, targetUri: Uri): Promise<void> {
const sourceFiles = await this.getAllFiles(sourceUri);
const targetFiles = await this.getAllFiles(targetUri);
// 复制新文件和更新的文件
for (const sourceFile of sourceFiles) {
const relativePath = this.getRelativePath(sourceUri, sourceFile);
const targetFile = Uri.joinPath(targetUri, relativePath);
const shouldCopy = await this.shouldCopyFile(sourceFile, targetFile);
if (shouldCopy) {
await this.ensureDirectoryExists(Uri.joinPath(targetUri, relativePath.split('/').slice(0, -1).join('/')));
await workspace.fs.copy(sourceFile, targetFile, { overwrite: true });
}
}
// 删除目标目录中多余的文件
for (const targetFile of targetFiles) {
const relativePath = this.getRelativePath(targetUri, targetFile);
const sourceFile = Uri.joinPath(sourceUri, relativePath);
if (!(await this.fileExists(sourceFile))) {
await workspace.fs.delete(targetFile);
}
}
}
private async shouldCopyFile(sourceUri: Uri, targetUri: Uri): Promise<boolean> {
try {
const sourceStat = await workspace.fs.stat(sourceUri);
const targetStat = await workspace.fs.stat(targetUri);
return sourceStat.mtime > targetStat.mtime;
} catch {
return true; // 目标文件不存在,需要复制
}
}
private async getAllFiles(uri: Uri): Promise<Uri[]> {
const files: Uri[] = [];
const entries = await workspace.fs.readDirectory(uri);
for (const [name, type] of entries) {
const childUri = Uri.joinPath(uri, name);
if (type === FileType.File) {
files.push(childUri);
} else if (type === FileType.Directory) {
const childFiles = await this.getAllFiles(childUri);
files.push(...childFiles);
}
}
return files;
}
private getRelativePath(baseUri: Uri, fileUri: Uri): string {
return fileUri.path.substring(baseUri.path.length + 1);
}
private async fileExists(uri: Uri): Promise<boolean> {
try {
await workspace.fs.stat(uri);
return true;
} catch {
return false;
}
}
private async ensureDirectoryExists(uri: Uri): Promise<void> {
try {
await workspace.fs.stat(uri);
} catch {
await workspace.fs.createDirectory(uri);
}
}
}错误处理
文件操作错误处理
typescript
class FileOperationError extends Error {
constructor(
message: string,
public readonly operation: string,
public readonly uri: Uri,
public readonly originalError?: Error
) {
super(message);
this.name = 'FileOperationError';
}
}
const safeFileOperation = async <T>(
operation: () => Promise<T>,
operationName: string,
uri: Uri
): Promise<T> => {
try {
return await operation();
} catch (error) {
throw new FileOperationError(
`Failed to ${operationName}: ${error.message}`,
operationName,
uri,
error
);
}
};
// 使用示例
const safeReadFile = async (uri: Uri): Promise<string> => {
return safeFileOperation(
async () => {
const content = await workspace.fs.readFile(uri);
return Buffer.from(content).toString('utf8');
},
'read file',
uri
);
};性能优化
文件操作缓存
typescript
class FileCache {
private cache = new Map<string, { content: string; mtime: number }>();
async readFile(uri: Uri): Promise<string> {
const key = uri.toString();
const stat = await workspace.fs.stat(uri);
const cached = this.cache.get(key);
if (cached && cached.mtime >= stat.mtime) {
return cached.content;
}
const content = await workspace.fs.readFile(uri);
const text = Buffer.from(content).toString('utf8');
this.cache.set(key, { content: text, mtime: stat.mtime });
return text;
}
invalidate(uri: Uri): void {
this.cache.delete(uri.toString());
}
clear(): void {
this.cache.clear();
}
}并发控制
typescript
class ConcurrentFileOperations {
private semaphore: number;
private queue: (() => Promise<any>)[] = [];
private running = 0;
constructor(maxConcurrent = 10) {
this.semaphore = maxConcurrent;
}
async execute<T>(operation: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push(async () => {
try {
const result = await operation();
resolve(result);
} catch (error) {
reject(error);
}
});
this.processQueue();
});
}
private async processQueue(): Promise<void> {
if (this.running >= this.semaphore || this.queue.length === 0) {
return;
}
const operation = this.queue.shift();
if (!operation) return;
this.running++;
try {
await operation();
} finally {
this.running--;
this.processQueue();
}
}
}