watcher - enable parcel watcher by default

This commit is contained in:
Benjamin Pasero 2021-10-28 09:59:03 +02:00
parent d781c69d7e
commit eb160fb65d
No known key found for this signature in database
GPG key ID: E6380CC4C8219E65
14 changed files with 5 additions and 785 deletions

View file

@ -102,10 +102,6 @@ const serverEntryPoints = [
name: 'vs/server/remoteExtensionHostProcess',
exclude: ['vs/css', 'vs/nls']
},
{
name: 'vs/platform/files/node/watcher/unix/watcherApp',
exclude: ['vs/css', 'vs/nls']
},
{
name: 'vs/platform/files/node/watcher/nsfw/watcherApp',
exclude: ['vs/css', 'vs/nls']

View file

@ -63,7 +63,6 @@
"@vscode/sqlite3": "4.0.12",
"@vscode/vscode-languagedetection": "1.0.21",
"applicationinsights": "1.0.8",
"chokidar": "3.5.1",
"graceful-fs": "4.2.8",
"http-proxy-agent": "^2.1.0",
"https-proxy-agent": "^2.2.3",
@ -97,7 +96,6 @@
"devDependencies": {
"7zip": "0.0.6",
"@types/applicationinsights": "0.20.0",
"@types/chokidar": "2.1.3",
"@types/cookie": "^0.3.3",
"@types/copy-webpack-plugin": "^6.0.3",
"@types/cssnano": "^4.0.0",

View file

@ -7,7 +7,6 @@
"@parcel/watcher": "2.0.0",
"@vscode/vscode-languagedetection": "1.0.21",
"applicationinsights": "1.0.8",
"chokidar": "3.5.1",
"cookie": "^0.4.0",
"graceful-fs": "4.2.8",
"http-proxy-agent": "^2.1.0",

View file

@ -127,14 +127,6 @@ agent-base@^4.3.0:
dependencies:
es6-promisify "^5.0.0"
anymatch@~3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142"
integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
applicationinsights@1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5"
@ -144,11 +136,6 @@ applicationinsights@1.0.8:
diagnostic-channel-publishers "0.2.1"
zone.js "0.7.6"
binary-extensions@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
bindings@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
@ -156,33 +143,11 @@ bindings@^1.5.0:
dependencies:
file-uri-to-path "1.0.0"
braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
chokidar@3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==
dependencies:
anymatch "~3.1.1"
braces "~3.0.2"
glob-parent "~5.1.0"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.5.0"
optionalDependencies:
fsevents "~2.3.1"
cookie@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
@ -260,13 +225,6 @@ file-uri-to-path@2:
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz#7b415aeba227d575851e0a5b0c640d7656403fba"
integrity sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
@ -276,11 +234,6 @@ fs-extra@^8.1.0:
jsonfile "^4.0.0"
universalify "^0.1.0"
fsevents@~2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.1.tgz#b209ab14c61012636c8863507edf7fb68cc54e9f"
integrity sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==
ftp@^0.3.10:
version "0.3.10"
resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d"
@ -301,13 +254,6 @@ get-uri@^3.0.2:
fs-extra "^8.1.0"
ftp "^0.3.10"
glob-parent@~5.1.0:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
graceful-fs@4.2.8:
version "4.2.8"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
@ -374,30 +320,6 @@ ip@^1.1.5:
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
@ -479,26 +401,11 @@ node-pty@0.11.0-beta7:
dependencies:
nan "^2.14.0"
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
picomatch@^2.0.4:
version "2.0.7"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6"
integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==
picomatch@^2.2.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
@ -514,13 +421,6 @@ readable-stream@1.1.x:
isarray "0.0.1"
string_decoder "~0.10.x"
readdirp@~3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
dependencies:
picomatch "^2.2.1"
semver@^5.3.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
@ -567,13 +467,6 @@ tas-client-umd@0.1.4:
resolved "https://registry.yarnpkg.com/tas-client-umd/-/tas-client-umd-0.1.4.tgz#49db4130dd63a8342fabf77185a740fc6a7bea80"
integrity sha512-1hFqJeLD3ryNikniIaO7TItlXhS5vx7bJ+wbPDf8o+IifgwwOWK2ARisdEM9SnJd0ccfcwNPG6Po+RiKn5L2hg==
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
import { isWindows } from 'vs/base/common/platform';
function testErrorMessage(module: string): string {
return `Unable to load "${module}" dependency. It was probably not compiled for the right operating system architecture or had missing build tools.`;
@ -53,17 +53,6 @@ suite('Native Modules (all platforms)', () => {
});
});
(!isMacintosh ? suite.skip : suite)('Native Modules (macOS)', () => {
test('chokidar (fsevents)', async () => {
const chokidar = await import('chokidar');
const watcher = chokidar.watch(__dirname);
assert.ok(watcher.options.useFsEvents, testErrorMessage('chokidar (fsevents)'));
return watcher.close();
});
});
(!isWindows ? suite.skip : suite)('Native Modules (Windows)', () => {
test('windows-mutex', async () => {

View file

@ -23,10 +23,8 @@ import { readFileIntoStream } from 'vs/platform/files/common/io';
import { FileWatcher as NodeJSWatcherService } from 'vs/platform/files/node/watcher/nodejs/watcherService';
import { FileWatcher as NsfwWatcherService } from 'vs/platform/files/node/watcher/nsfw/watcherService';
import { FileWatcher as ParcelWatcherService } from 'vs/platform/files/node/watcher/parcel/watcherService';
import { FileWatcher as UnixWatcherService } from 'vs/platform/files/node/watcher/unix/watcherService';
import { IDiskFileChange, ILogMessage, IWatchRequest, WatcherService } from 'vs/platform/files/common/watcher';
import { ILogService } from 'vs/platform/log/common/log';
import product from 'vs/platform/product/common/product';
import { AbstractDiskFileSystemProvider } from 'vs/platform/files/common/diskFileSystemProvider';
import { toErrorMessage } from 'vs/base/common/errorMessage';
@ -570,25 +568,13 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
let enableLegacyWatcher = false;
if (this.options?.watcher?.usePolling) {
enableLegacyWatcher = false; // can use Parcel watcher for when polling is required
enableLegacyWatcher = false; // must use Parcel watcher for when polling is required
} else {
if (this.options?.legacyWatcher === 'on' || this.options?.legacyWatcher === 'off') {
enableLegacyWatcher = this.options.legacyWatcher === 'on'; // setting always wins
} else {
if (product.quality === 'stable') {
// in stable use legacy for single folder workspaces
// TODO@bpasero remove me eventually
enableLegacyWatcher = folders === 1;
}
}
enableLegacyWatcher = this.options?.legacyWatcher === 'on'; // setting always wins
}
if (enableLegacyWatcher) {
if (isLinux) {
watcherImpl = UnixWatcherService;
} else {
watcherImpl = NsfwWatcherService;
}
watcherImpl = NsfwWatcherService;
} else {
watcherImpl = ParcelWatcherService;
}

View file

@ -1,374 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as chokidar from 'chokidar';
import * as fs from 'fs';
import * as gracefulFs from 'graceful-fs';
import { equals } from 'vs/base/common/arrays';
import { ThrottledDelayer } from 'vs/base/common/async';
import { Emitter } from 'vs/base/common/event';
import { isEqualOrParent } from 'vs/base/common/extpath';
import { match, parse, ParsedPattern } from 'vs/base/common/glob';
import { Disposable } from 'vs/base/common/lifecycle';
import { normalizeNFC } from 'vs/base/common/normalization';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { realcaseSync } from 'vs/base/node/extpath';
import { FileChangeType } from 'vs/platform/files/common/files';
import { IWatcherOptions, IWatcherService } from 'vs/platform/files/node/watcher/unix/watcher';
import { IDiskFileChange, ILogMessage, IWatchRequest, normalizeFileChanges } from 'vs/platform/files/common/watcher';
gracefulFs.gracefulify(fs); // enable gracefulFs
process.noAsar = true; // disable ASAR support in watcher process
interface IWatcher {
requests: ExtendedWatcherRequest[];
stop(): Promise<void>;
}
interface ExtendedWatcherRequest extends IWatchRequest {
parsedPattern?: ParsedPattern;
}
export class ChokidarWatcherService extends Disposable implements IWatcherService {
private static readonly FS_EVENT_DELAY = 50; // aggregate and only emit events when changes have stopped for this duration (in ms)
private static readonly EVENT_SPAM_WARNING_THRESHOLD = 60 * 1000; // warn after certain time span of event spam
private readonly _onDidChangeFile = this._register(new Emitter<IDiskFileChange[]>());
readonly onDidChangeFile = this._onDidChangeFile.event;
private readonly _onDidLogMessage = this._register(new Emitter<ILogMessage>());
readonly onDidLogMessage = this._onDidLogMessage.event;
private watchers = new Map<string, IWatcher>();
private _watcherCount = 0;
get wacherCount() { return this._watcherCount; }
private pollingInterval?: number;
private usePolling?: boolean | string[];
private verboseLogging: boolean | undefined;
private spamCheckStartTime: number | undefined;
private spamWarningLogged: boolean | undefined;
private enospcErrorLogged: boolean | undefined;
async init(options: IWatcherOptions): Promise<void> {
this.pollingInterval = options.pollingInterval;
this.usePolling = options.usePolling;
this.watchers.clear();
this._watcherCount = 0;
this.verboseLogging = options.verboseLogging;
}
async setVerboseLogging(enabled: boolean): Promise<void> {
this.verboseLogging = enabled;
}
async watch(requests: IWatchRequest[]): Promise<void> {
const watchers = new Map<string, IWatcher>();
const newRequests: string[] = [];
const requestsByBasePath = normalizeRoots(requests);
// evaluate new & remaining watchers
for (const basePath in requestsByBasePath) {
const watcher = this.watchers.get(basePath);
if (watcher && isEqualRequests(watcher.requests, requestsByBasePath[basePath])) {
watchers.set(basePath, watcher);
this.watchers.delete(basePath);
} else {
newRequests.push(basePath);
}
}
// stop all old watchers
for (const [, watcher] of this.watchers) {
await watcher.stop();
}
// start all new watchers
for (const basePath of newRequests) {
const requests = requestsByBasePath[basePath];
watchers.set(basePath, this.doWatch(basePath, requests));
}
this.watchers = watchers;
}
private doWatch(basePath: string, requests: IWatchRequest[]): IWatcher {
const pollingInterval = this.pollingInterval || 5000;
let usePolling = this.usePolling; // boolean or a list of path patterns
if (Array.isArray(usePolling)) {
// switch to polling if one of the paths matches with a watched path
usePolling = usePolling.some(pattern => requests.some(request => match(pattern, request.path)));
}
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,
disableGlobbing: true // fix https://github.com/microsoft/vscode/issues/4586
};
const excludes: string[] = [];
const isSingleFolder = requests.length === 1;
if (isSingleFolder) {
excludes.push(...requests[0].excludes); // if there's only one request, use the built-in ignore-filterering
}
if ((isMacintosh || isLinux) && (basePath.length === 0 || basePath === '/')) {
excludes.push('/dev/**');
if (isLinux) {
excludes.push('/proc/**', '/sys/**');
}
}
excludes.push('**/*.asar'); // Ensure we never recurse into ASAR archives
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})`);
}
this.debug(`Start watching: ${realBasePath}, excludes: ${excludes.join(',')}, usePolling: ${usePolling ? 'true, interval ' + pollingInterval : 'false'}`);
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: async () => {
try {
if (this.verboseLogging) {
this.log(`Stop watching: ${basePath}]`);
}
if (chokidarWatcher) {
await 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;
}
}
const 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 && typeof this.spamCheckStartTime === 'number' && 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(async () => {
const events = undeliveredFileEvents;
undeliveredFileEvents = [];
// Broadcast to clients normalized
const normalizedEvents = normalizeFileChanges(events);
this._onDidChangeFile.fire(normalizedEvents);
// Logging
if (this.verboseLogging) {
for (const e of normalizedEvents) {
this.log(` >> normalized ${e.type === FileChangeType.ADDED ? '[ADDED]' : e.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${e.path}`);
}
}
return 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;
}
async stop(): Promise<void> {
for (const [, watcher] of this.watchers) {
await watcher.stop();
}
this.watchers.clear();
}
private log(message: string) {
this._onDidLogMessage.fire({ type: 'trace', message: `[File Watcher (chokidar)] ` + message });
}
private debug(message: string) {
this._onDidLogMessage.fire({ type: 'debug', message: `[File Watcher (chokidar)] ` + message });
}
private warn(message: string) {
this._onDidLogMessage.fire({ type: 'warn', message: `[File Watcher (chokidar)] ` + message });
}
private error(message: string) {
this._onDidLogMessage.fire({ type: 'error', message: `[File Watcher (chokidar)] ` + message });
}
}
function isIgnored(path: string, requests: ExtendedWatcherRequest[]): boolean {
for (const request of requests) {
if (request.path === path) {
return false;
}
if (isEqualOrParent(path, request.path)) {
if (!request.parsedPattern) {
if (request.excludes && request.excludes.length > 0) {
const pattern = `{${request.excludes.join(',')}}`;
request.parsedPattern = parse(pattern);
} else {
request.parsedPattern = () => false;
}
}
const relPath = path.substr(request.path.length + 1);
if (!request.parsedPattern(relPath)) {
return false;
}
}
}
return true;
}
/**
* Normalizes a set of root paths by grouping by the most parent root path.
* equests with Sub paths are skipped if they have the same ignored set as the parent.
*/
export function normalizeRoots(requests: IWatchRequest[]): { [basePath: string]: IWatchRequest[] } {
requests = requests.sort((r1, r2) => r1.path.localeCompare(r2.path));
let prevRequest: IWatchRequest | null = null;
const result: { [basePath: string]: IWatchRequest[] } = Object.create(null);
for (const request of requests) {
const basePath = request.path;
const ignored = (request.excludes || []).sort();
if (prevRequest && (isEqualOrParent(basePath, prevRequest.path))) {
if (!isEqualIgnore(ignored, prevRequest.excludes)) {
result[prevRequest.path].push({ path: basePath, excludes: ignored });
}
} else {
prevRequest = { path: basePath, excludes: ignored };
result[basePath] = [prevRequest];
}
}
return result;
}
function isEqualRequests(r1: readonly IWatchRequest[], r2: readonly IWatchRequest[]) {
return equals(r1, r2, (a, b) => a.path === b.path && isEqualIgnore(a.excludes, b.excludes));
}
function isEqualIgnore(i1: readonly string[], i2: readonly string[]) {
return equals(i1, i2);
}

View file

@ -1,96 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as platform from 'vs/base/common/platform';
import { IWatchRequest } from 'vs/platform/files/common/watcher';
suite('Chokidar normalizeRoots', async () => {
// Load `chokidarWatcherService` within the suite to prevent all tests
// from failing to start if `chokidar` was not properly installed
const { normalizeRoots } = await import('vs/platform/files/node/watcher/unix/chokidarWatcherService');
function newRequest(basePath: string, ignored: string[] = []): IWatchRequest {
return { path: basePath, excludes: ignored };
}
function assertNormalizedRootPath(inputPaths: string[], expectedPaths: string[]) {
const requests = inputPaths.map(path => newRequest(path));
const actual = normalizeRoots(requests);
assert.deepStrictEqual(Object.keys(actual).sort(), expectedPaths);
}
function assertNormalizedRequests(inputRequests: IWatchRequest[], expectedRequests: { [path: string]: IWatchRequest[] }) {
const actual = normalizeRoots(inputRequests);
const actualPath = Object.keys(actual).sort();
const expectedPaths = Object.keys(expectedRequests).sort();
assert.deepStrictEqual(actualPath, expectedPaths);
for (let path of actualPath) {
let a = expectedRequests[path].sort((r1, r2) => r1.path.localeCompare(r2.path));
let e = expectedRequests[path].sort((r1, r2) => r1.path.localeCompare(r2.path));
assert.deepStrictEqual(a, e);
}
}
test('should not impacts roots that don\'t overlap', () => {
if (platform.isWindows) {
assertNormalizedRootPath(['C:\\a'], ['C:\\a']);
assertNormalizedRootPath(['C:\\a', 'C:\\b'], ['C:\\a', 'C:\\b']);
assertNormalizedRootPath(['C:\\a', 'C:\\b', 'C:\\c\\d\\e'], ['C:\\a', 'C:\\b', 'C:\\c\\d\\e']);
} else {
assertNormalizedRootPath(['/a'], ['/a']);
assertNormalizedRootPath(['/a', '/b'], ['/a', '/b']);
assertNormalizedRootPath(['/a', '/b', '/c/d/e'], ['/a', '/b', '/c/d/e']);
}
});
test('should remove sub-folders of other roots', () => {
if (platform.isWindows) {
assertNormalizedRootPath(['C:\\a', 'C:\\a\\b'], ['C:\\a']);
assertNormalizedRootPath(['C:\\a', 'C:\\b', 'C:\\a\\b'], ['C:\\a', 'C:\\b']);
assertNormalizedRootPath(['C:\\b\\a', 'C:\\a', 'C:\\b', 'C:\\a\\b'], ['C:\\a', 'C:\\b']);
assertNormalizedRootPath(['C:\\a', 'C:\\a\\b', 'C:\\a\\c\\d'], ['C:\\a']);
} else {
assertNormalizedRootPath(['/a', '/a/b'], ['/a']);
assertNormalizedRootPath(['/a', '/b', '/a/b'], ['/a', '/b']);
assertNormalizedRootPath(['/b/a', '/a', '/b', '/a/b'], ['/a', '/b']);
assertNormalizedRootPath(['/a', '/a/b', '/a/c/d'], ['/a']);
assertNormalizedRootPath(['/a/c/d/e', '/a/b/d', '/a/c/d', '/a/c/e/f', '/a/b'], ['/a/b', '/a/c/d', '/a/c/e/f']);
}
});
test('should remove duplicates', () => {
if (platform.isWindows) {
assertNormalizedRootPath(['C:\\a', 'C:\\a\\', 'C:\\a'], ['C:\\a']);
} else {
assertNormalizedRootPath(['/a', '/a/', '/a'], ['/a']);
assertNormalizedRootPath(['/a', '/b', '/a/b'], ['/a', '/b']);
assertNormalizedRootPath(['/b/a', '/a', '/b', '/a/b'], ['/a', '/b']);
assertNormalizedRootPath(['/a', '/a/b', '/a/c/d'], ['/a']);
}
});
test('nested requests', () => {
let p1, p2, p3;
if (platform.isWindows) {
p1 = 'C:\\a';
p2 = 'C:\\a\\b';
p3 = 'C:\\a\\b\\c';
} else {
p1 = '/a';
p2 = '/a/b';
p3 = '/a/b/c';
}
const r1 = newRequest(p1, ['**/*.ts']);
const r2 = newRequest(p2, ['**/*.js']);
const r3 = newRequest(p3, ['**/*.ts']);
assertNormalizedRequests([r1, r2], { [p1]: [r1, r2] });
assertNormalizedRequests([r2, r1], { [p1]: [r1, r2] });
assertNormalizedRequests([r1, r2, r3], { [p1]: [r1, r2, r3] });
assertNormalizedRequests([r1, r3], { [p1]: [r1] });
assertNormalizedRequests([r2, r3], { [p2]: [r2, r3] });
});
});

View file

@ -1,26 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { IDiskFileChange, ILogMessage, IWatchRequest } from 'vs/platform/files/common/watcher';
export interface IWatcherOptions {
pollingInterval?: number;
usePolling?: boolean | string[]; // boolean or a set of glob patterns matching folders that need polling
verboseLogging?: boolean;
}
export interface IWatcherService {
readonly onDidChangeFile: Event<IDiskFileChange[]>;
readonly onDidLogMessage: Event<ILogMessage>;
init(options: IWatcherOptions): Promise<void>;
watch(paths: IWatchRequest[]): Promise<void>;
setVerboseLogging(enabled: boolean): Promise<void>;
stop(): Promise<void>;
}

View file

@ -1,12 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { Server } from 'vs/base/parts/ipc/node/ipc.cp';
import { ChokidarWatcherService } from 'vs/platform/files/node/watcher/unix/chokidarWatcherService';
const server = new Server('watcher');
const service = new ChokidarWatcherService();
server.registerChannel('watcher', ProxyChannel.fromService(service));

View file

@ -1,98 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { FileAccess } from 'vs/base/common/network';
import { getNextTickChannel, ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import { IWatcherOptions, IWatcherService } from 'vs/platform/files/node/watcher/unix/watcher';
import { IDiskFileChange, ILogMessage, IWatchRequest, WatcherService } from 'vs/platform/files/common/watcher';
/**
* @deprecated
*/
export class FileWatcher extends WatcherService {
private static readonly MAX_RESTARTS = 5;
private service: IWatcherService | undefined;
private isDisposed = false;
private restartCounter = 0;
private requests: IWatchRequest[] | undefined = undefined;
constructor(
private readonly onDidFilesChange: (changes: IDiskFileChange[]) => void,
private readonly onLogMessage: (msg: ILogMessage) => void,
private verboseLogging: boolean,
private readonly watcherOptions: IWatcherOptions = {}
) {
super();
this.startWatching();
}
private startWatching(): void {
const client = this._register(new Client(
FileAccess.asFileUri('bootstrap-fork', require).fsPath,
{
serverName: 'File Watcher (chokidar)',
args: ['--type=watcherServiceChokidar'],
env: {
VSCODE_AMD_ENTRYPOINT: 'vs/platform/files/node/watcher/unix/watcherApp',
VSCODE_PIPE_LOGGING: 'true',
VSCODE_VERBOSE_LOGGING: 'true' // transmit console logs from server to client
}
}
));
this._register(client.onDidProcessExit(() => {
// our watcher app should never be completed because it keeps on watching. being in here indicates
// that the watcher process died and we want to restart it here. we only do it a max number of times
if (!this.isDisposed) {
if (this.restartCounter <= FileWatcher.MAX_RESTARTS && this.requests) {
this.error('terminated unexpectedly and is restarted again...');
this.restartCounter++;
this.startWatching();
this.service?.watch(this.requests);
} else {
this.error('failed to start after retrying for some time, giving up. Please report this as a bug report!');
}
}
}));
// Initialize watcher
this.service = ProxyChannel.toService<IWatcherService>(getNextTickChannel(client.getChannel('watcher')));
this.service.init({ ...this.watcherOptions, verboseLogging: this.verboseLogging });
// Wire in event handlers
this._register(this.service.onDidChangeFile(e => !this.isDisposed && this.onDidFilesChange(e)));
this._register(this.service.onDidLogMessage(e => this.onLogMessage(e)));
}
async setVerboseLogging(verboseLogging: boolean): Promise<void> {
this.verboseLogging = verboseLogging;
if (!this.isDisposed) {
await this.service?.setVerboseLogging(verboseLogging);
}
}
error(message: string) {
this.onLogMessage({ type: 'error', message: `[File Watcher (chokidar)] ${message}` });
}
async watch(requests: IWatchRequest[]): Promise<void> {
this.requests = requests;
await this.service?.watch(requests);
}
override dispose(): void {
this.isDisposed = true;
super.dispose();
}
}

View file

@ -14,7 +14,6 @@ exports.collectModules = function () {
createModuleDescription('vs/workbench/services/search/node/searchApp'),
createModuleDescription('vs/platform/files/node/watcher/unix/watcherApp'),
createModuleDescription('vs/platform/files/node/watcher/nsfw/watcherApp'),
createModuleDescription('vs/platform/files/node/watcher/parcel/watcherApp'),

View file

@ -266,7 +266,7 @@ configurationRegistry.registerConfiguration({
'markdownEnumDescriptions': [
nls.localize('files.legacyWatcher.on', "Enable the legacy file watcher in case you see issues with the new file watcher."),
nls.localize('files.legacyWatcher.off', "Disable the legacy file watcher and enable the new file watcher to benefit from its capabilities."),
nls.localize('files.legacyWatcher.default', "The new file watcher will be enabled if you are using insiders version or whenever you open multi-root workspaces."),
nls.localize('files.legacyWatcher.default', "The new file watcher will be enabled."),
],
'default': 'default',
'description': nls.localize('legacyWatcher', "Controls the mechanism used for file watching. Only change this when you see issues related to file watching."),

View file

@ -492,13 +492,6 @@
dependencies:
applicationinsights "*"
"@types/chokidar@2.1.3":
version "2.1.3"
resolved "https://registry.yarnpkg.com/@types/chokidar/-/chokidar-2.1.3.tgz#123ab795dba6d89be04bf076e6aecaf8620db674"
integrity sha512-6qK3xoLLAhQVTucQGHTySwOVA1crHRXnJeLwqK6KIFkkKa2aoMFXh+WEi8PotxDtvN6MQJLyYN9ag9P6NLV81w==
dependencies:
chokidar "*"
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
@ -2163,21 +2156,6 @@ charenc@~0.0.1:
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
chokidar@*:
version "3.2.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.3.tgz#b9270a565d14f02f6bfdd537a6a2bbf5549b8c8c"
integrity sha512-GtrxGuRf6bzHQmXWRepvsGnXpkQkVU+D2/9a7dAe4a7v1NhrfZOZ2oKf76M3nOs46fFYL8D+Q8JYA4GYeJ8Cjw==
dependencies:
anymatch "~3.1.1"
braces "~3.0.2"
glob-parent "~5.1.0"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.2.0"
optionalDependencies:
fsevents "~2.1.1"
chokidar@3.4.3:
version "3.4.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b"
@ -4358,11 +4336,6 @@ fsevents@^1.2.7:
bindings "^1.5.0"
nan "^2.12.1"
fsevents@~2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.1.tgz#74c64e21df71721845d0c44fe54b7f56b82995a9"
integrity sha512-4FRPXWETxtigtJW/gxzEDsX1LVbPAM93VleB83kZB+ellqbHMkyt2aJfuzNLRvFPnGi6bcE5SvfxgbXPeKteJw==
fsevents@~2.1.2:
version "2.1.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
@ -8405,13 +8378,6 @@ readdirp@^2.2.1:
micromatch "^3.1.10"
readable-stream "^2.0.2"
readdirp@~3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839"
integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==
dependencies:
picomatch "^2.0.4"
readdirp@~3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"