feat(watchers): Improve write stabilization and ignore temporary editor files
This commit is contained in:
@@ -20,10 +20,24 @@ export class NodeWatcher implements IWatcher {
|
||||
constructor(private options: IWatcherOptions) {
|
||||
this.writeStabilizer = new WriteStabilizer(
|
||||
options.stabilityThreshold,
|
||||
options.pollInterval
|
||||
options.pollInterval,
|
||||
options.maxWaitTime
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file is a temporary file created by editors
|
||||
*/
|
||||
private isTemporaryFile(filePath: string): boolean {
|
||||
const basename = path.basename(filePath);
|
||||
// Editor temp files: *.tmp.*, *.swp, *.swx, *~, .#*
|
||||
if (basename.includes('.tmp.')) return true;
|
||||
if (basename.endsWith('.swp') || basename.endsWith('.swx')) return true;
|
||||
if (basename.endsWith('~')) return true;
|
||||
if (basename.startsWith('.#')) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
get isWatching(): boolean {
|
||||
return this._isWatching;
|
||||
}
|
||||
@@ -114,6 +128,11 @@ export class NodeWatcher implements IWatcher {
|
||||
): Promise<void> {
|
||||
const fullPath = path.join(basePath, filename);
|
||||
|
||||
// Skip temporary files created by editors (atomic saves)
|
||||
if (this.isTemporaryFile(fullPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Throttle duplicate events
|
||||
if (!this.shouldEmit(fullPath, eventType)) {
|
||||
return;
|
||||
@@ -132,23 +151,14 @@ export class NodeWatcher implements IWatcher {
|
||||
this.events$.next({ type: 'addDir', path: fullPath, stats });
|
||||
}
|
||||
} else {
|
||||
// Wait for write to stabilize before emitting
|
||||
try {
|
||||
const stableStats = await this.writeStabilizer.waitForWriteFinish(fullPath);
|
||||
const wasWatched = this.watchedFiles.has(fullPath);
|
||||
this.watchedFiles.add(fullPath);
|
||||
this.events$.next({
|
||||
type: wasWatched ? 'change' : 'add',
|
||||
path: fullPath,
|
||||
stats: stableStats
|
||||
});
|
||||
} catch {
|
||||
// File was deleted during stabilization
|
||||
if (this.watchedFiles.has(fullPath)) {
|
||||
this.watchedFiles.delete(fullPath);
|
||||
this.events$.next({ type: 'unlink', path: fullPath });
|
||||
}
|
||||
}
|
||||
// Rename events (atomic saves) don't need stabilization - file is already complete
|
||||
const wasWatched = this.watchedFiles.has(fullPath);
|
||||
this.watchedFiles.add(fullPath);
|
||||
this.events$.next({
|
||||
type: wasWatched ? 'change' : 'add',
|
||||
path: fullPath,
|
||||
stats
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// File doesn't exist - it was deleted
|
||||
@@ -162,7 +172,7 @@ export class NodeWatcher implements IWatcher {
|
||||
}
|
||||
}
|
||||
} else if (eventType === 'change') {
|
||||
// File was modified
|
||||
// File was modified in-place - use stabilization for streaming writes
|
||||
if (stats && !stats.isDirectory()) {
|
||||
try {
|
||||
const stableStats = await this.writeStabilizer.waitForWriteFinish(fullPath);
|
||||
|
||||
Reference in New Issue
Block a user