Files API
The Files API provides functionality for file system operations, file watching, and file management in Trae.
Overview
The Files API enables you to:
- Read and write files
- Watch for file system changes
- Manage file permissions and metadata
- Handle file operations (copy, move, delete)
- Work with directories and file trees
- Implement file providers and virtual file systems
- Handle file encoding and binary data
- Manage file associations and content types
Basic Usage
File Manager
typescript
import { TraeAPI } from '@trae/api';
import * as path from 'path';
import * as fs from 'fs/promises';
// File manager
class FileManager {
private watchers: Map<string, TraeAPI.FileSystemWatcher> = new Map();
private fileProviders: Map<string, FileProvider> = new Map();
private eventEmitter = new TraeAPI.EventEmitter<FileEvent>();
private disposables: TraeAPI.Disposable[] = [];
private fileCache: Map<string, CachedFile> = new Map();
private fileAssociations: Map<string, FileAssociation> = new Map();
private contentTypeDetectors: ContentTypeDetector[] = [];
private fileOperationQueue: FileOperation[] = [];
private isProcessingQueue = false;
constructor() {
this.setupBuiltinProviders();
this.setupFileAssociations();
this.setupContentTypeDetectors();
this.setupEventHandlers();
}
// File reading
async readFile(uri: TraeAPI.Uri): Promise<Uint8Array> {
try {
const cached = this.fileCache.get(uri.toString());
if (cached && !this.isFileModified(uri, cached.lastModified)) {
return cached.content;
}
const content = await TraeAPI.workspace.fs.readFile(uri);
// Cache the file
this.cacheFile(uri, content);
this.eventEmitter.fire({ type: 'fileRead', uri, size: content.length });
return content;
} catch (error) {
this.eventEmitter.fire({ type: 'fileReadError', uri, error });
throw error;
}
}
async readTextFile(uri: TraeAPI.Uri, encoding: string = 'utf8'): Promise<string> {
const content = await this.readFile(uri);
return Buffer.from(content).toString(encoding);
}
async readJsonFile<T = any>(uri: TraeAPI.Uri): Promise<T> {
const content = await this.readTextFile(uri);
try {
return JSON.parse(content);
} catch (error) {
throw new Error(`Invalid JSON in file ${uri.fsPath}: ${error}`);
}
}
// File writing
async writeFile(uri: TraeAPI.Uri, content: Uint8Array): Promise<void> {
try {
await this.queueFileOperation({
type: 'write',
uri,
content,
timestamp: new Date()
});
await TraeAPI.workspace.fs.writeFile(uri, content);
// Update cache
this.cacheFile(uri, content);
this.eventEmitter.fire({ type: 'fileWritten', uri, size: content.length });
} catch (error) {
this.eventEmitter.fire({ type: 'fileWriteError', uri, error });
throw error;
}
}
async writeTextFile(uri: TraeAPI.Uri, content: string, encoding: string = 'utf8'): Promise<void> {
const buffer = Buffer.from(content, encoding);
await this.writeFile(uri, new Uint8Array(buffer));
}
async writeJsonFile(uri: TraeAPI.Uri, content: any, indent: number = 2): Promise<void> {
const jsonString = JSON.stringify(content, null, indent);
await this.writeTextFile(uri, jsonString);
}
// File operations
async copyFile(source: TraeAPI.Uri, destination: TraeAPI.Uri, overwrite: boolean = false): Promise<void> {
try {
await this.queueFileOperation({
type: 'copy',
source,
destination,
overwrite,
timestamp: new Date()
});
await TraeAPI.workspace.fs.copy(source, destination, { overwrite });
this.eventEmitter.fire({ type: 'fileCopied', source, destination });
} catch (error) {
this.eventEmitter.fire({ type: 'fileCopyError', source, destination, error });
throw error;
}
}
async moveFile(source: TraeAPI.Uri, destination: TraeAPI.Uri, overwrite: boolean = false): Promise<void> {
try {
await this.queueFileOperation({
type: 'move',
source,
destination,
overwrite,
timestamp: new Date()
});
await TraeAPI.workspace.fs.rename(source, destination, { overwrite });
// Update cache
const cached = this.fileCache.get(source.toString());
if (cached) {
this.fileCache.delete(source.toString());
this.fileCache.set(destination.toString(), {
...cached,
uri: destination
});
}
this.eventEmitter.fire({ type: 'fileMoved', source, destination });
} catch (error) {
this.eventEmitter.fire({ type: 'fileMoveError', source, destination, error });
throw error;
}
}
async deleteFile(uri: TraeAPI.Uri, recursive: boolean = false): Promise<void> {
try {
await this.queueFileOperation({
type: 'delete',
uri,
recursive,
timestamp: new Date()
});
await TraeAPI.workspace.fs.delete(uri, { recursive, useTrash: true });
// Remove from cache
this.fileCache.delete(uri.toString());
this.eventEmitter.fire({ type: 'fileDeleted', uri });
} catch (error) {
this.eventEmitter.fire({ type: 'fileDeleteError', uri, error });
throw error;
}
}
// Directory operations
async createDirectory(uri: TraeAPI.Uri): Promise<void> {
try {
await TraeAPI.workspace.fs.createDirectory(uri);
this.eventEmitter.fire({ type: 'directoryCreated', uri });
} catch (error) {
this.eventEmitter.fire({ type: 'directoryCreateError', uri, error });
throw error;
}
}
async readDirectory(uri: TraeAPI.Uri): Promise<[string, TraeAPI.FileType][]> {
try {
const entries = await TraeAPI.workspace.fs.readDirectory(uri);
this.eventEmitter.fire({ type: 'directoryRead', uri, entryCount: entries.length });
return entries;
} catch (error) {
this.eventEmitter.fire({ type: 'directoryReadError', uri, error });
throw error;
}
}
async getDirectoryTree(uri: TraeAPI.Uri, maxDepth: number = 10): Promise<FileTreeNode> {
const stat = await this.stat(uri);
const node: FileTreeNode = {
uri,
name: path.basename(uri.fsPath),
type: stat.type,
size: stat.size,
lastModified: stat.mtime,
children: []
};
if (stat.type === TraeAPI.FileType.Directory && maxDepth > 0) {
try {
const entries = await this.readDirectory(uri);
for (const [name, type] of entries) {
const childUri = TraeAPI.Uri.joinPath(uri, name);
const childNode = await this.getDirectoryTree(childUri, maxDepth - 1);
node.children!.push(childNode);
}
// Sort children: directories first, then files
node.children!.sort((a, b) => {
if (a.type !== b.type) {
return a.type === TraeAPI.FileType.Directory ? -1 : 1;
}
return a.name.localeCompare(b.name);
});
} catch (error) {
// Directory might not be readable
console.warn(`Cannot read directory ${uri.fsPath}:`, error);
}
}
return node;
}
// File information
async stat(uri: TraeAPI.Uri): Promise<TraeAPI.FileStat> {
try {
const stat = await TraeAPI.workspace.fs.stat(uri);
this.eventEmitter.fire({ type: 'fileStat', uri, stat });
return stat;
} catch (error) {
this.eventEmitter.fire({ type: 'fileStatError', uri, error });
throw error;
}
}
async exists(uri: TraeAPI.Uri): Promise<boolean> {
try {
await this.stat(uri);
return true;
} catch {
return false;
}
}
async isFile(uri: TraeAPI.Uri): Promise<boolean> {
try {
const stat = await this.stat(uri);
return stat.type === TraeAPI.FileType.File;
} catch {
return false;
}
}
async isDirectory(uri: TraeAPI.Uri): Promise<boolean> {
try {
const stat = await this.stat(uri);
return stat.type === TraeAPI.FileType.Directory;
} catch {
return false;
}
}
// File watching
watchFile(uri: TraeAPI.Uri, options?: FileWatchOptions): TraeAPI.Disposable {
const watcherId = uri.toString();
if (this.watchers.has(watcherId)) {
throw new Error(`File is already being watched: ${uri.fsPath}`);
}
const pattern = new TraeAPI.RelativePattern(
TraeAPI.workspace.getWorkspaceFolder(uri) || TraeAPI.workspace.workspaceFolders![0],
path.relative(TraeAPI.workspace.workspaceFolders![0].uri.fsPath, uri.fsPath)
);
const watcher = TraeAPI.workspace.createFileSystemWatcher(pattern);
const disposables: TraeAPI.Disposable[] = [];
if (options?.watchCreate !== false) {
disposables.push(watcher.onDidCreate(uri => {
this.eventEmitter.fire({ type: 'fileCreated', uri });
options?.onCreate?.(uri);
}));
}
if (options?.watchChange !== false) {
disposables.push(watcher.onDidChange(uri => {
// Invalidate cache
this.fileCache.delete(uri.toString());
this.eventEmitter.fire({ type: 'fileChanged', uri });
options?.onChange?.(uri);
}));
}
if (options?.watchDelete !== false) {
disposables.push(watcher.onDidDelete(uri => {
// Remove from cache
this.fileCache.delete(uri.toString());
this.eventEmitter.fire({ type: 'fileDeleted', uri });
options?.onDelete?.(uri);
}));
}
this.watchers.set(watcherId, watcher);
this.eventEmitter.fire({ type: 'watcherCreated', uri });
return {
dispose: () => {
watcher.dispose();
disposables.forEach(d => d.dispose());
this.watchers.delete(watcherId);
this.eventEmitter.fire({ type: 'watcherDisposed', uri });
}
};
}
watchDirectory(uri: TraeAPI.Uri, options?: DirectoryWatchOptions): TraeAPI.Disposable {
const watcherId = uri.toString();
if (this.watchers.has(watcherId)) {
throw new Error(`Directory is already being watched: ${uri.fsPath}`);
}
const pattern = new TraeAPI.RelativePattern(
TraeAPI.workspace.getWorkspaceFolder(uri) || TraeAPI.workspace.workspaceFolders![0],
path.relative(TraeAPI.workspace.workspaceFolders![0].uri.fsPath, uri.fsPath) + '/**'
);
const watcher = TraeAPI.workspace.createFileSystemWatcher(pattern);
const disposables: TraeAPI.Disposable[] = [];
disposables.push(watcher.onDidCreate(uri => {
this.eventEmitter.fire({ type: 'fileCreated', uri });
options?.onCreate?.(uri);
}));
disposables.push(watcher.onDidChange(uri => {
// Invalidate cache
this.fileCache.delete(uri.toString());
this.eventEmitter.fire({ type: 'fileChanged', uri });
options?.onChange?.(uri);
}));
disposables.push(watcher.onDidDelete(uri => {
// Remove from cache
this.fileCache.delete(uri.toString());
this.eventEmitter.fire({ type: 'fileDeleted', uri });
options?.onDelete?.(uri);
}));
this.watchers.set(watcherId, watcher);
this.eventEmitter.fire({ type: 'watcherCreated', uri });
return {
dispose: () => {
watcher.dispose();
disposables.forEach(d => d.dispose());
this.watchers.delete(watcherId);
this.eventEmitter.fire({ type: 'watcherDisposed', uri });
}
};
}
// File search
async findFiles(
include: TraeAPI.GlobPattern,
exclude?: TraeAPI.GlobPattern,
maxResults?: number,
token?: TraeAPI.CancellationToken
): Promise<TraeAPI.Uri[]> {
try {
const files = await TraeAPI.workspace.findFiles(include, exclude, maxResults, token);
this.eventEmitter.fire({ type: 'filesFound', pattern: include.toString(), count: files.length });
return files;
} catch (error) {
this.eventEmitter.fire({ type: 'fileSearchError', pattern: include.toString(), error });
throw error;
}
}
async searchInFiles(
query: string,
options?: SearchOptions
): Promise<SearchResult[]> {
const results: SearchResult[] = [];
const files = await this.findFiles(
options?.include || '**/*',
options?.exclude,
options?.maxResults
);
for (const file of files) {
try {
const content = await this.readTextFile(file);
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const matches = this.findMatches(line, query, options);
for (const match of matches) {
results.push({
uri: file,
line: i + 1,
column: match.start + 1,
text: line,
match: {
start: match.start,
end: match.end,
text: match.text
}
});
}
}
} catch (error) {
// Skip files that can't be read
console.warn(`Cannot search in file ${file.fsPath}:`, error);
}
}
this.eventEmitter.fire({ type: 'searchCompleted', query, resultCount: results.length });
return results;
}
private findMatches(text: string, query: string, options?: SearchOptions): TextMatch[] {
const matches: TextMatch[] = [];
if (options?.useRegex) {
try {
const flags = options.caseSensitive ? 'g' : 'gi';
const regex = new RegExp(query, flags);
let match;
while ((match = regex.exec(text)) !== null) {
matches.push({
start: match.index,
end: match.index + match[0].length,
text: match[0]
});
}
} catch (error) {
// Invalid regex, fall back to literal search
return this.findLiteralMatches(text, query, options);
}
} else {
return this.findLiteralMatches(text, query, options);
}
return matches;
}
private findLiteralMatches(text: string, query: string, options?: SearchOptions): TextMatch[] {
const matches: TextMatch[] = [];
const searchText = options?.caseSensitive ? text : text.toLowerCase();
const searchQuery = options?.caseSensitive ? query : query.toLowerCase();
let index = 0;
while ((index = searchText.indexOf(searchQuery, index)) !== -1) {
matches.push({
start: index,
end: index + query.length,
text: text.substring(index, index + query.length)
});
index += query.length;
}
return matches;
}
// File associations
registerFileAssociation(association: FileAssociation): void {
this.fileAssociations.set(association.pattern, association);
this.eventEmitter.fire({ type: 'fileAssociationRegistered', association });
}
getFileAssociation(uri: TraeAPI.Uri): FileAssociation | undefined {
const fileName = path.basename(uri.fsPath);
for (const [pattern, association] of this.fileAssociations) {
if (this.matchesPattern(fileName, pattern)) {
return association;
}
}
return undefined;
}
private matchesPattern(fileName: string, pattern: string): boolean {
// Simple glob pattern matching
const regex = new RegExp(
'^' + pattern
.replace(/\./g, '\\.')
.replace(/\*/g, '.*')
.replace(/\?/g, '.') + '$',
'i'
);
return regex.test(fileName);
}
// Content type detection
registerContentTypeDetector(detector: ContentTypeDetector): void {
this.contentTypeDetectors.push(detector);
this.eventEmitter.fire({ type: 'contentTypeDetectorRegistered', detector });
}
async detectContentType(uri: TraeAPI.Uri): Promise<string> {
// Try registered detectors first
for (const detector of this.contentTypeDetectors) {
try {
const contentType = await detector.detect(uri);
if (contentType) {
return contentType;
}
} catch (error) {
console.warn('Content type detector failed:', error);
}
}
// Fall back to file extension
const ext = path.extname(uri.fsPath).toLowerCase();
return this.getContentTypeByExtension(ext);
}
private getContentTypeByExtension(extension: string): string {
const mimeTypes: { [key: string]: string } = {
'.txt': 'text/plain',
'.md': 'text/markdown',
'.json': 'application/json',
'.js': 'text/javascript',
'.ts': 'text/typescript',
'.html': 'text/html',
'.css': 'text/css',
'.xml': 'text/xml',
'.yaml': 'text/yaml',
'.yml': 'text/yaml',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.pdf': 'application/pdf',
'.zip': 'application/zip'
};
return mimeTypes[extension] || 'application/octet-stream';
}
// File operations queue
private async queueFileOperation(operation: FileOperation): Promise<void> {
this.fileOperationQueue.push(operation);
if (!this.isProcessingQueue) {
await this.processFileOperationQueue();
}
}
private async processFileOperationQueue(): Promise<void> {
this.isProcessingQueue = true;
try {
while (this.fileOperationQueue.length > 0) {
const operation = this.fileOperationQueue.shift()!;
try {
await this.executeFileOperation(operation);
} catch (error) {
console.error('File operation failed:', operation, error);
}
}
} finally {
this.isProcessingQueue = false;
}
}
private async executeFileOperation(operation: FileOperation): Promise<void> {
// File operations are handled by the calling methods
// This is a placeholder for additional operation processing
this.eventEmitter.fire({ type: 'fileOperationExecuted', operation });
}
// Cache management
private cacheFile(uri: TraeAPI.Uri, content: Uint8Array): void {
this.fileCache.set(uri.toString(), {
uri,
content,
lastModified: new Date(),
size: content.length
});
}
private async isFileModified(uri: TraeAPI.Uri, cachedTime: Date): Promise<boolean> {
try {
const stat = await this.stat(uri);
return stat.mtime > cachedTime.getTime();
} catch {
return true; // Assume modified if we can't check
}
}
clearCache(): void {
this.fileCache.clear();
this.eventEmitter.fire({ type: 'cacheCleared' });
}
getCacheSize(): number {
return this.fileCache.size;
}
getCacheStats(): CacheStats {
let totalSize = 0;
for (const cached of this.fileCache.values()) {
totalSize += cached.size;
}
return {
entryCount: this.fileCache.size,
totalSize,
averageSize: this.fileCache.size > 0 ? totalSize / this.fileCache.size : 0
};
}
// Utility methods
private setupBuiltinProviders(): void {
// Setup built-in file providers
}
private setupFileAssociations(): void {
// Register common file associations
this.registerFileAssociation({
pattern: '*.ts',
language: 'typescript',
icon: 'typescript',
description: 'TypeScript file'
});
this.registerFileAssociation({
pattern: '*.js',
language: 'javascript',
icon: 'javascript',
description: 'JavaScript file'
});
this.registerFileAssociation({
pattern: '*.json',
language: 'json',
icon: 'json',
description: 'JSON file'
});
this.registerFileAssociation({
pattern: '*.md',
language: 'markdown',
icon: 'markdown',
description: 'Markdown file'
});
}
private setupContentTypeDetectors(): void {
// Register built-in content type detectors
this.registerContentTypeDetector({
detect: async (uri: TraeAPI.Uri) => {
// Detect by file signature/magic bytes
try {
const content = await this.readFile(uri);
if (content.length >= 4) {
// PNG signature
if (content[0] === 0x89 && content[1] === 0x50 && content[2] === 0x4E && content[3] === 0x47) {
return 'image/png';
}
// JPEG signature
if (content[0] === 0xFF && content[1] === 0xD8) {
return 'image/jpeg';
}
// PDF signature
if (content[0] === 0x25 && content[1] === 0x50 && content[2] === 0x44 && content[3] === 0x46) {
return 'application/pdf';
}
}
} catch {
// Ignore errors
}
return undefined;
}
});
}
private setupEventHandlers(): void {
// Setup additional event handlers
}
// Event handling
onDidChangeFile(listener: (event: FileEvent) => void): TraeAPI.Disposable {
return this.eventEmitter.event(listener);
}
// Dispose
dispose(): void {
// Dispose all watchers
for (const watcher of this.watchers.values()) {
watcher.dispose();
}
this.watchers.clear();
// Dispose other resources
this.disposables.forEach(d => d.dispose());
this.disposables = [];
// Clear caches
this.fileCache.clear();
this.fileAssociations.clear();
this.contentTypeDetectors = [];
this.fileOperationQueue = [];
this.eventEmitter.dispose();
}
}
// Initialize file manager
const fileManager = new FileManager();Advanced File Operations
File Provider Implementation
typescript
// Custom file provider
class CustomFileProvider implements FileProvider {
private scheme: string;
private files: Map<string, VirtualFile> = new Map();
private eventEmitter = new TraeAPI.EventEmitter<TraeAPI.FileChangeEvent>();
constructor(scheme: string) {
this.scheme = scheme;
}
get onDidChangeFile(): TraeAPI.Event<TraeAPI.FileChangeEvent> {
return this.eventEmitter.event;
}
watch(uri: TraeAPI.Uri, options: { recursive: boolean; excludes: string[] }): TraeAPI.Disposable {
// Implement file watching for virtual files
return {
dispose: () => {
// Cleanup watcher
}
};
}
stat(uri: TraeAPI.Uri): TraeAPI.FileStat | Thenable<TraeAPI.FileStat> {
const file = this.files.get(uri.toString());
if (!file) {
throw TraeAPI.FileSystemError.FileNotFound(uri);
}
return {
type: file.type,
ctime: file.created.getTime(),
mtime: file.modified.getTime(),
size: file.content.length
};
}
readDirectory(uri: TraeAPI.Uri): [string, TraeAPI.FileType][] | Thenable<[string, TraeAPI.FileType][]> {
const entries: [string, TraeAPI.FileType][] = [];
const prefix = uri.toString() + '/';
for (const [path, file] of this.files) {
if (path.startsWith(prefix)) {
const relativePath = path.substring(prefix.length);
if (!relativePath.includes('/')) {
entries.push([relativePath, file.type]);
}
}
}
return entries;
}
createDirectory(uri: TraeAPI.Uri): void | Thenable<void> {
this.files.set(uri.toString(), {
type: TraeAPI.FileType.Directory,
content: new Uint8Array(),
created: new Date(),
modified: new Date()
});
this.eventEmitter.fire({
type: TraeAPI.FileChangeType.Created,
uri
});
}
readFile(uri: TraeAPI.Uri): Uint8Array | Thenable<Uint8Array> {
const file = this.files.get(uri.toString());
if (!file) {
throw TraeAPI.FileSystemError.FileNotFound(uri);
}
if (file.type !== TraeAPI.FileType.File) {
throw TraeAPI.FileSystemError.FileIsADirectory(uri);
}
return file.content;
}
writeFile(
uri: TraeAPI.Uri,
content: Uint8Array,
options: { create: boolean; overwrite: boolean }
): void | Thenable<void> {
const exists = this.files.has(uri.toString());
if (exists && !options.overwrite) {
throw TraeAPI.FileSystemError.FileExists(uri);
}
if (!exists && !options.create) {
throw TraeAPI.FileSystemError.FileNotFound(uri);
}
const now = new Date();
this.files.set(uri.toString(), {
type: TraeAPI.FileType.File,
content,
created: exists ? this.files.get(uri.toString())!.created : now,
modified: now
});
this.eventEmitter.fire({
type: exists ? TraeAPI.FileChangeType.Changed : TraeAPI.FileChangeType.Created,
uri
});
}
delete(uri: TraeAPI.Uri, options: { recursive: boolean }): void | Thenable<void> {
const file = this.files.get(uri.toString());
if (!file) {
throw TraeAPI.FileSystemError.FileNotFound(uri);
}
if (file.type === TraeAPI.FileType.Directory && !options.recursive) {
// Check if directory is empty
const prefix = uri.toString() + '/';
for (const path of this.files.keys()) {
if (path.startsWith(prefix)) {
throw TraeAPI.FileSystemError.NoPermissions('Directory not empty');
}
}
}
// Delete file and all children if recursive
const toDelete = [uri.toString()];
if (options.recursive) {
const prefix = uri.toString() + '/';
for (const path of this.files.keys()) {
if (path.startsWith(prefix)) {
toDelete.push(path);
}
}
}
for (const path of toDelete) {
this.files.delete(path);
}
this.eventEmitter.fire({
type: TraeAPI.FileChangeType.Deleted,
uri
});
}
rename(
oldUri: TraeAPI.Uri,
newUri: TraeAPI.Uri,
options: { overwrite: boolean }
): void | Thenable<void> {
const file = this.files.get(oldUri.toString());
if (!file) {
throw TraeAPI.FileSystemError.FileNotFound(oldUri);
}
const newExists = this.files.has(newUri.toString());
if (newExists && !options.overwrite) {
throw TraeAPI.FileSystemError.FileExists(newUri);
}
// Move file
this.files.set(newUri.toString(), file);
this.files.delete(oldUri.toString());
// Move children if directory
if (file.type === TraeAPI.FileType.Directory) {
const oldPrefix = oldUri.toString() + '/';
const newPrefix = newUri.toString() + '/';
const toMove: [string, VirtualFile][] = [];
for (const [path, childFile] of this.files) {
if (path.startsWith(oldPrefix)) {
toMove.push([newPrefix + path.substring(oldPrefix.length), childFile]);
}
}
for (const [oldPath] of toMove) {
this.files.delete(oldPath);
}
for (const [newPath, childFile] of toMove) {
this.files.set(newPath, childFile);
}
}
this.eventEmitter.fire({
type: TraeAPI.FileChangeType.Deleted,
uri: oldUri
});
this.eventEmitter.fire({
type: TraeAPI.FileChangeType.Created,
uri: newUri
});
}
copy?(
source: TraeAPI.Uri,
destination: TraeAPI.Uri,
options: { overwrite: boolean }
): void | Thenable<void> {
const file = this.files.get(source.toString());
if (!file) {
throw TraeAPI.FileSystemError.FileNotFound(source);
}
const destExists = this.files.has(destination.toString());
if (destExists && !options.overwrite) {
throw TraeAPI.FileSystemError.FileExists(destination);
}
// Copy file
const now = new Date();
this.files.set(destination.toString(), {
...file,
created: now,
modified: now,
content: new Uint8Array(file.content) // Deep copy content
});
// Copy children if directory
if (file.type === TraeAPI.FileType.Directory) {
const sourcePrefix = source.toString() + '/';
const destPrefix = destination.toString() + '/';
for (const [path, childFile] of this.files) {
if (path.startsWith(sourcePrefix)) {
const relativePath = path.substring(sourcePrefix.length);
this.files.set(destPrefix + relativePath, {
...childFile,
created: now,
modified: now,
content: new Uint8Array(childFile.content)
});
}
}
}
this.eventEmitter.fire({
type: TraeAPI.FileChangeType.Created,
uri: destination
});
}
}
// Register custom file provider
const customProvider = new CustomFileProvider('custom');
TraeAPI.workspace.registerFileSystemProvider('custom', customProvider);Interface Definitions
typescript
// File tree node
interface FileTreeNode {
uri: TraeAPI.Uri;
name: string;
type: TraeAPI.FileType;
size: number;
lastModified: number;
children?: FileTreeNode[];
}
// File watch options
interface FileWatchOptions {
watchCreate?: boolean;
watchChange?: boolean;
watchDelete?: boolean;
onCreate?(uri: TraeAPI.Uri): void;
onChange?(uri: TraeAPI.Uri): void;
onDelete?(uri: TraeAPI.Uri): void;
}
interface DirectoryWatchOptions {
onCreate?(uri: TraeAPI.Uri): void;
onChange?(uri: TraeAPI.Uri): void;
onDelete?(uri: TraeAPI.Uri): void;
}
// Search options
interface SearchOptions {
include?: TraeAPI.GlobPattern;
exclude?: TraeAPI.GlobPattern;
maxResults?: number;
caseSensitive?: boolean;
useRegex?: boolean;
}
interface SearchResult {
uri: TraeAPI.Uri;
line: number;
column: number;
text: string;
match: TextMatch;
}
interface TextMatch {
start: number;
end: number;
text: string;
}
// File association
interface FileAssociation {
pattern: string;
language?: string;
icon?: string;
description?: string;
defaultEditor?: string;
}
// Content type detector
interface ContentTypeDetector {
detect(uri: TraeAPI.Uri): Promise<string | undefined>;
}
// File provider
interface FileProvider extends TraeAPI.FileSystemProvider {
// Additional methods can be added here
}
// Virtual file
interface VirtualFile {
type: TraeAPI.FileType;
content: Uint8Array;
created: Date;
modified: Date;
}
// Cached file
interface CachedFile {
uri: TraeAPI.Uri;
content: Uint8Array;
lastModified: Date;
size: number;
}
// Cache stats
interface CacheStats {
entryCount: number;
totalSize: number;
averageSize: number;
}
// File operation
interface FileOperation {
type: 'read' | 'write' | 'copy' | 'move' | 'delete';
uri?: TraeAPI.Uri;
source?: TraeAPI.Uri;
destination?: TraeAPI.Uri;
content?: Uint8Array;
overwrite?: boolean;
recursive?: boolean;
timestamp: Date;
}
// File event types
interface FileEvent {
type: 'fileRead' | 'fileReadError' | 'fileWritten' | 'fileWriteError' |
'fileCopied' | 'fileCopyError' | 'fileMoved' | 'fileMoveError' |
'fileDeleted' | 'fileDeleteError' | 'fileCreated' | 'fileChanged' |
'directoryCreated' | 'directoryCreateError' | 'directoryRead' | 'directoryReadError' |
'fileStat' | 'fileStatError' | 'watcherCreated' | 'watcherDisposed' |
'filesFound' | 'fileSearchError' | 'searchCompleted' |
'fileAssociationRegistered' | 'contentTypeDetectorRegistered' |
'fileOperationExecuted' | 'cacheCleared';
uri?: TraeAPI.Uri;
source?: TraeAPI.Uri;
destination?: TraeAPI.Uri;
size?: number;
error?: any;
stat?: TraeAPI.FileStat;
pattern?: string;
count?: number;
entryCount?: number;
query?: string;
resultCount?: number;
association?: FileAssociation;
detector?: ContentTypeDetector;
operation?: FileOperation;
}API Reference
FileManager
Methods
readFile(uri): Promise<Uint8Array>- Read file as bytesreadTextFile(uri, encoding?): Promise<string>- Read file as textreadJsonFile<T>(uri): Promise<T>- Read and parse JSON filewriteFile(uri, content): Promise<void>- Write bytes to filewriteTextFile(uri, content, encoding?): Promise<void>- Write text to filewriteJsonFile(uri, content, indent?): Promise<void>- Write JSON to filecopyFile(source, destination, overwrite?): Promise<void>- Copy filemoveFile(source, destination, overwrite?): Promise<void>- Move filedeleteFile(uri, recursive?): Promise<void>- Delete filecreateDirectory(uri): Promise<void>- Create directoryreadDirectory(uri): Promise<[string, FileType][]>- Read directory entriesgetDirectoryTree(uri, maxDepth?): Promise<FileTreeNode>- Get directory treestat(uri): Promise<FileStat>- Get file statisticsexists(uri): Promise<boolean>- Check if file existsisFile(uri): Promise<boolean>- Check if path is fileisDirectory(uri): Promise<boolean>- Check if path is directorywatchFile(uri, options?): Disposable- Watch file for changeswatchDirectory(uri, options?): Disposable- Watch directory for changesfindFiles(include, exclude?, maxResults?, token?): Promise<Uri[]>- Find files by patternsearchInFiles(query, options?): Promise<SearchResult[]>- Search text in filesregisterFileAssociation(association): void- Register file associationgetFileAssociation(uri): FileAssociation | undefined- Get file associationregisterContentTypeDetector(detector): void- Register content type detectordetectContentType(uri): Promise<string>- Detect file content typeclearCache(): void- Clear file cachegetCacheSize(): number- Get cache entry countgetCacheStats(): CacheStats- Get cache statistics
Events
onDidChangeFile(listener): Disposable- File system changes
Best Practices
- Error Handling: Always handle file system errors gracefully
- Performance: Use caching for frequently accessed files
- Memory Management: Dispose file watchers when no longer needed
- Security: Validate file paths and prevent directory traversal
- Encoding: Specify encoding explicitly when working with text files
- Large Files: Use streaming for large file operations
- Concurrency: Queue file operations to prevent conflicts
- Backup: Create backups before destructive operations
- Permissions: Check file permissions before operations
- Cross-platform: Use path utilities for cross-platform compatibility
Related APIs
- Workspace API - Workspace file management
- Editor API - Text editor file operations
- Search API - File and text search
- Git API - Version control file operations
- Terminal API - Terminal file operations
- Tasks API - Task file dependencies
- Extensions API - Extension file management
- Settings API - Configuration files