private _watch()

in src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts [101:284]


	private _watch(basePath: string, requests: IWatcherRequest[]): IWatcher {
		if (this._verboseLogging) {
			this.log(`Start watching: ${basePath}]`);
		}

		const pollingInterval = this._pollingInterval || 5000;
		const usePolling = this._usePolling;
		if (usePolling && this._verboseLogging) {
			this.log(`Use polling instead of fs.watch: Polling interval ${pollingInterval} ms`);
		}

		const watcherOpts: chokidar.WatchOptions = {
			ignoreInitial: true,
			ignorePermissionErrors: true,
			followSymlinks: true, // this is the default of chokidar and supports file events through symlinks
			interval: pollingInterval, // while not used in normal cases, if any error causes chokidar to fallback to polling, increase its intervals
			binaryInterval: pollingInterval,
			usePolling: usePolling,
			disableGlobbing: true // fix https://github.com/Microsoft/vscode/issues/4586
		};

		const excludes: string[] = [];
		// if there's only one request, use the built-in ignore-filterering
		const isSingleFolder = requests.length === 1;
		if (isSingleFolder) {
			excludes.push(...requests[0].excludes);
		}

		if ((isMacintosh || isLinux) && (basePath.length === 0 || basePath === '/')) {
			excludes.push('/dev/**');
			if (isLinux) {
				excludes.push('/proc/**', '/sys/**');
			}
		}
		watcherOpts.ignored = excludes;

		// Chokidar fails when the basePath does not match case-identical to the path on disk
		// so we have to find the real casing of the path and do some path massaging to fix this
		// see https://github.com/paulmillr/chokidar/issues/418
		const realBasePath = isMacintosh ? (realcaseSync(basePath) || basePath) : basePath;
		const realBasePathLength = realBasePath.length;
		const realBasePathDiffers = (basePath !== realBasePath);

		if (realBasePathDiffers) {
			this.warn(`Watcher basePath does not match version on disk and was corrected (original: ${basePath}, real: ${realBasePath})`);
		}

		let chokidarWatcher: chokidar.FSWatcher | null = chokidar.watch(realBasePath, watcherOpts);
		this._watcherCount++;

		// Detect if for some reason the native watcher library fails to load
		if (isMacintosh && chokidarWatcher.options && !chokidarWatcher.options.useFsEvents) {
			this.warn('Watcher is not using native fsevents library and is falling back to unefficient polling.');
		}

		let undeliveredFileEvents: IDiskFileChange[] = [];
		let fileEventDelayer: ThrottledDelayer<undefined> | null = new ThrottledDelayer(ChokidarWatcherService.FS_EVENT_DELAY);

		const watcher: IWatcher = {
			requests,
			stop: () => {
				try {
					if (this._verboseLogging) {
						this.log(`Stop watching: ${basePath}]`);
					}
					if (chokidarWatcher) {
						chokidarWatcher.close();
						this._watcherCount--;
						chokidarWatcher = null;
					}
					if (fileEventDelayer) {
						fileEventDelayer.cancel();
						fileEventDelayer = null;
					}
				} catch (error) {
					this.warn('Error while stopping watcher: ' + error.toString());
				}
			}
		};

		chokidarWatcher.on('all', (type: string, path: string) => {
			if (isMacintosh) {
				// Mac: uses NFD unicode form on disk, but we want NFC
				// See also https://github.com/nodejs/node/issues/2165
				path = normalizeNFC(path);
			}

			if (path.indexOf(realBasePath) < 0) {
				return; // we really only care about absolute paths here in our basepath context here
			}

			// Make sure to convert the path back to its original basePath form if the realpath is different
			if (realBasePathDiffers) {
				path = basePath + path.substr(realBasePathLength);
			}

			let eventType: FileChangeType;
			switch (type) {
				case 'change':
					eventType = FileChangeType.UPDATED;
					break;
				case 'add':
				case 'addDir':
					eventType = FileChangeType.ADDED;
					break;
				case 'unlink':
				case 'unlinkDir':
					eventType = FileChangeType.DELETED;
					break;
				default:
					return;
			}

			// if there's more than one request we need to do
			// extra filtering due to potentially overlapping roots
			if (!isSingleFolder) {
				if (isIgnored(path, watcher.requests)) {
					return;
				}
			}

			let event = { type: eventType, path };

			// Logging
			if (this._verboseLogging) {
				this.log(`${eventType === FileChangeType.ADDED ? '[ADDED]' : eventType === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${path}`);
			}

			// Check for spam
			const now = Date.now();
			if (undeliveredFileEvents.length === 0) {
				this.spamWarningLogged = false;
				this.spamCheckStartTime = now;
			} else if (!this.spamWarningLogged && this.spamCheckStartTime + ChokidarWatcherService.EVENT_SPAM_WARNING_THRESHOLD < now) {
				this.spamWarningLogged = true;
				this.warn(`Watcher is busy catching up with ${undeliveredFileEvents.length} file changes in 60 seconds. Latest changed path is "${event.path}"`);
			}

			// Add to buffer
			undeliveredFileEvents.push(event);

			if (fileEventDelayer) {
				// Delay and send buffer
				fileEventDelayer.trigger(() => {
					const events = undeliveredFileEvents;
					undeliveredFileEvents = [];

					// 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);
				});
			}
		});

		chokidarWatcher.on('error', (error: NodeJS.ErrnoException) => {
			if (error) {

				// 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 (error.code === 'ENOSPC') {
					if (!this.enospcErrorLogged) {
						this.enospcErrorLogged = true;
						this.stop();
						this.error('Inotify limit reached (ENOSPC)');
					}
				} else {
					this.warn(error.toString());
				}
			}
		});
		return watcher;
	}