in src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts [52:177]
private _watch(request: IWatcherRequest): void {
let undeliveredFileEvents: IDiskFileChange[] = [];
const fileEventDelayer = new ThrottledDelayer<void>(NsfwWatcherService.FS_EVENT_DELAY);
let readyPromiseResolve: (watcher: IWatcherObjet) => void;
this._pathWatchers[request.path] = {
ready: new Promise<IWatcherObjet>(resolve => readyPromiseResolve = resolve),
ignored: Array.isArray(request.excludes) ? request.excludes.map(ignored => glob.parse(ignored)) : []
};
process.on('uncaughtException', (e: Error | string) => {
// Specially handle ENOSPC errors that can happen when
// the watcher consumes so many file descriptors that
// we are running into a limit. We only want to warn
// once in this case to avoid log spam.
// See https://github.com/Microsoft/vscode/issues/7950
if (e === 'Inotify limit reached' && !this.enospcErrorLogged) {
this.enospcErrorLogged = true;
this.error('Inotify limit reached (ENOSPC)');
}
});
// NSFW does not report file changes in the path provided on macOS if
// - the path uses wrong casing
// - the path is a symbolic link
// We have to detect this case and massage the events to correct this.
let realBasePathDiffers = false;
let realBasePathLength = request.path.length;
if (platform.isMacintosh) {
try {
// First check for symbolic link
let realBasePath = realpathSync(request.path);
// Second check for casing difference
if (request.path === realBasePath) {
realBasePath = (realcaseSync(request.path) || request.path);
}
if (request.path !== realBasePath) {
realBasePathLength = realBasePath.length;
realBasePathDiffers = true;
this.warn(`Watcher basePath does not match version on disk and will be corrected (original: ${request.path}, real: ${realBasePath})`);
}
} catch (error) {
// ignore
}
}
nsfw(request.path, events => {
for (const e of events) {
// Logging
if (this._verboseLogging) {
const logPath = e.action === nsfw.actions.RENAMED ? path.join(e.directory, e.oldFile || '') + ' -> ' + e.newFile : path.join(e.directory, e.file || '');
this.log(`${e.action === nsfw.actions.CREATED ? '[CREATED]' : e.action === nsfw.actions.DELETED ? '[DELETED]' : e.action === nsfw.actions.MODIFIED ? '[CHANGED]' : '[RENAMED]'} ${logPath}`);
}
// Convert nsfw event to IRawFileChange and add to queue
let absolutePath: string;
if (e.action === nsfw.actions.RENAMED) {
// Rename fires when a file's name changes within a single directory
absolutePath = path.join(e.directory, e.oldFile || '');
if (!this._isPathIgnored(absolutePath, this._pathWatchers[request.path].ignored)) {
undeliveredFileEvents.push({ type: FileChangeType.DELETED, path: absolutePath });
} else if (this._verboseLogging) {
this.log(` >> ignored ${absolutePath}`);
}
absolutePath = path.join(e.newDirectory || e.directory, e.newFile || '');
if (!this._isPathIgnored(absolutePath, this._pathWatchers[request.path].ignored)) {
undeliveredFileEvents.push({ type: FileChangeType.ADDED, path: absolutePath });
} else if (this._verboseLogging) {
this.log(` >> ignored ${absolutePath}`);
}
} else {
absolutePath = path.join(e.directory, e.file || '');
if (!this._isPathIgnored(absolutePath, this._pathWatchers[request.path].ignored)) {
undeliveredFileEvents.push({
type: nsfwActionToRawChangeType[e.action],
path: absolutePath
});
} else if (this._verboseLogging) {
this.log(` >> ignored ${absolutePath}`);
}
}
}
// Delay and send buffer
fileEventDelayer.trigger(() => {
const events = undeliveredFileEvents;
undeliveredFileEvents = [];
if (platform.isMacintosh) {
events.forEach(e => {
// Mac uses NFD unicode form on disk, but we want NFC
e.path = normalizeNFC(e.path);
// Convert paths back to original form in case it differs
if (realBasePathDiffers) {
e.path = request.path + e.path.substr(realBasePathLength);
}
});
}
// Broadcast to clients normalized
const res = normalizeFileChanges(events);
this._onWatchEvent.fire(res);
// Logging
if (this._verboseLogging) {
res.forEach(r => {
this.log(` >> normalized ${r.type === FileChangeType.ADDED ? '[ADDED]' : r.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${r.path}`);
});
}
return Promise.resolve(undefined);
});
}).then(watcher => {
this._pathWatchers[request.path].watcher = watcher;
const startPromise = watcher.start();
startPromise.then(() => readyPromiseResolve(watcher));
return startPromise;
});
}