Merge pull request #136207 from microsoft/ben/nov

November debt
This commit is contained in:
Benjamin Pasero 2021-11-01 08:17:13 +01:00 committed by GitHub
commit 9375113a83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 288 additions and 914 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

@ -190,6 +190,29 @@ suite('vscode API - window', () => {
}
});
test.only('editor, opening multiple at the same time #134786', async () => {
const fileA = await createRandomFile();
const fileB = await createRandomFile();
const fileC = await createRandomFile();
const testFiles = [fileA, fileB, fileC];
const result = await Promise.all(testFiles.map(async testFile => {
try {
const doc = await workspace.openTextDocument(testFile);
const editor = await window.showTextDocument(doc);
return editor.document.uri;
} catch (error) {
return undefined;
}
}));
assert.strictEqual(result.length, 3);
assert.strictEqual(result[0], undefined);
assert.strictEqual(result[1], undefined);
assert.strictEqual(result[2]?.toString(), fileC.toString());
});
test('default column when opening a file', async () => {
const [docA, docB, docC] = await Promise.all([
workspace.openTextDocument(await createRandomFile()),

View file

@ -63,8 +63,7 @@
"@vscode/sqlite3": "4.0.12",
"@vscode/vscode-languagedetection": "1.0.21",
"applicationinsights": "1.0.8",
"chokidar": "3.5.1",
"graceful-fs": "4.2.6",
"graceful-fs": "4.2.8",
"http-proxy-agent": "^2.1.0",
"https-proxy-agent": "^2.2.3",
"iconv-lite-umd": "0.6.8",
@ -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,9 +7,8 @@
"@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.6",
"graceful-fs": "4.2.8",
"http-proxy-agent": "^2.1.0",
"https-proxy-agent": "^2.2.3",
"iconv-lite-umd": "0.6.8",

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,14 +254,12 @@ 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"
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
graceful-fs@4.2.6, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.6"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
@ -369,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"
@ -474,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"
@ -509,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"
@ -562,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

@ -162,8 +162,8 @@ export interface IWatchRequest {
excludes: string[];
/**
* @deprecated TODO@bpasero TODO@aeschli remove me once WSL1
* support ends.
* @deprecated this only exists for WSL1 support and should never
* be used in any other case.
*/
pollingInterval?: number;
}

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';
@ -48,8 +46,8 @@ export interface IWatcherOptions {
* If `true`, will enable polling for all watchers, otherwise
* will enable it for paths included in the string array.
*
* @deprecated TODO@bpasero TODO@aeschli remove me once WSL1
* support ends.
* @deprecated this only exists for WSL1 support and should never
* be used in any other case.
*/
usePolling: boolean | string[];
@ -57,8 +55,8 @@ export interface IWatcherOptions {
* If polling is enabled (via `usePolling`), defines the duration
* in which the watcher will poll for changes.
*
* @deprecated TODO@bpasero TODO@aeschli remove me once WSL1
* support ends.
* @deprecated this only exists for WSL1 support and should never
* be used in any other case.
*/
pollingInterval?: number;
}
@ -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

@ -20,6 +20,32 @@ export interface ISharedProcessWorkerProcess {
type: string;
}
export interface IOnDidTerminateSharedProcessWorkerProcess {
/**
* More information around how the shared process worker
* process terminated. Will be `undefined` in case the
* worker process was terminated normally via APIs
* and will be defined in case the worker process
* terminated on its own, either unexpectedly or
* because it finished.
*/
reason?: ISharedProcessWorkerProcessExit;
}
export interface ISharedProcessWorkerProcessExit {
/**
* The shared process worker process exit code if known.
*/
code?: number;
/**
* The shared process worker process exit signal if known.
*/
signal?: string;
}
export interface ISharedProcessWorkerConfiguration {
/**
@ -77,8 +103,12 @@ export interface ISharedProcessWorkerService {
* the same process from one window. The intent of these workers is to be reused per
* window and the communication channel allows to dynamically update the processes
* after the fact.
*
* @returns a promise that resolves then the worker terminated. Provides more details
* about the termination that can be used to figure out if the termination was unexpected
* or not and whether the worker needs to be restarted.
*/
createWorker(configuration: ISharedProcessWorkerConfiguration): Promise<void>;
createWorker(configuration: ISharedProcessWorkerConfiguration): Promise<IOnDidTerminateSharedProcessWorkerProcess>;
/**
* Terminates the process for the provided configuration if any.

View file

@ -11,8 +11,9 @@ import { toErrorMessage } from 'vs/base/common/errorMessage';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { deepClone } from 'vs/base/common/objects';
import { withNullAsUndefined } from 'vs/base/common/types';
import { removeDangerousEnvVariables } from 'vs/base/node/processes';
import { hash, ISharedProcessWorkerConfiguration } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
import { hash, ISharedProcessWorkerConfiguration, ISharedProcessWorkerProcessExit } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
import { SharedProcessWorkerMessages, ISharedProcessToWorkerMessage, ISharedProcessWorkerEnvironment, IWorkerToSharedProcessMessage } from 'vs/platform/sharedProcess/electron-browser/sharedProcessWorker';
/**
@ -75,10 +76,11 @@ class SharedProcessWorkerMain {
process.spawn();
// Handle self termination of the child process
const listener = Event.once(process.onDidProcessSelfTerminate)(() => {
const listener = Event.once(process.onDidProcessSelfTerminate)(reason => {
send({
id: SharedProcessWorkerMessages.SelfTerminated,
configuration
configuration,
message: JSON.stringify(reason)
});
});
@ -108,7 +110,7 @@ class SharedProcessWorkerMain {
class SharedProcessWorkerProcess extends Disposable {
private readonly _onDidProcessSelfTerminate = this._register(new Emitter<void>());
private readonly _onDidProcessSelfTerminate = this._register(new Emitter<ISharedProcessWorkerProcessExit>());
readonly onDidProcessSelfTerminate = this._onDidProcessSelfTerminate.event;
private child: ChildProcess | undefined = undefined;
@ -151,7 +153,10 @@ class SharedProcessWorkerProcess extends Disposable {
this.child = undefined;
this._onDidProcessSelfTerminate.fire();
this._onDidProcessSelfTerminate.fire({
code: withNullAsUndefined(code),
signal: withNullAsUndefined(signal)
});
}));
const onMessageEmitter = this._register(new Emitter<VSBuffer>());

View file

@ -6,11 +6,11 @@
import { ipcRenderer } from 'electron';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Emitter } from 'vs/base/common/event';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
import { FileAccess } from 'vs/base/common/network';
import { generateUuid } from 'vs/base/common/uuid';
import { ILogService } from 'vs/platform/log/common/log';
import { hash, ISharedProcessWorkerConfiguration, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
import { hash, IOnDidTerminateSharedProcessWorkerProcess, ISharedProcessWorkerConfiguration, ISharedProcessWorkerProcessExit, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
import { SharedProcessWorkerMessages, ISharedProcessToWorkerMessage, IWorkerToSharedProcessMessage } from 'vs/platform/sharedProcess/electron-browser/sharedProcessWorker';
export class SharedProcessWorkerService implements ISharedProcessWorkerService {
@ -18,23 +18,25 @@ export class SharedProcessWorkerService implements ISharedProcessWorkerService {
declare readonly _serviceBrand: undefined;
private readonly workers = new Map<string /* process module ID */, Promise<SharedProcessWebWorker>>();
private readonly processes = new Map<number /* process configuration hash */, IDisposable>();
private readonly processeDisposables = new Map<number /* process configuration hash */, (reason?: ISharedProcessWorkerProcessExit) => void>();
private readonly processResolvers = new Map<number /* process configuration hash */, (process: IOnDidTerminateSharedProcessWorkerProcess) => void>();
constructor(
@ILogService private readonly logService: ILogService
) {
}
async createWorker(configuration: ISharedProcessWorkerConfiguration): Promise<void> {
async createWorker(configuration: ISharedProcessWorkerConfiguration): Promise<IOnDidTerminateSharedProcessWorkerProcess> {
const workerLogId = `window: ${configuration.reply.windowId}, moduleId: ${configuration.process.moduleId}`;
this.logService.trace(`SharedProcess: createWorker (${workerLogId})`);
// Ensure to dispose any existing process for config
const configurationHash = hash(configuration);
if (this.processes.has(configurationHash)) {
if (this.processeDisposables.has(configurationHash)) {
this.logService.warn(`SharedProcess: createWorker found an existing worker that will be terminated (${workerLogId})`);
this.disposeWorker(configuration);
this.doDisposeWorker(configuration);
}
const cts = new CancellationTokenSource();
@ -43,8 +45,8 @@ export class SharedProcessWorkerService implements ISharedProcessWorkerService {
let windowPort: MessagePort | undefined = undefined;
let workerPort: MessagePort | undefined = undefined;
// Store as process for later disposal
this.processes.set(configurationHash, toDisposable(() => {
// Store as process for termination support
this.processeDisposables.set(configurationHash, (reason?: ISharedProcessWorkerProcessExit) => {
// Signal to token
cts.dispose(true);
@ -57,14 +59,27 @@ export class SharedProcessWorkerService implements ISharedProcessWorkerService {
workerPort?.close();
// Remove from processes
this.processes.delete(configurationHash);
}));
this.processeDisposables.delete(configurationHash);
// Release process resolvers if any
const processResolver = this.processResolvers.get(configurationHash);
if (processResolver) {
this.processResolvers.delete(configurationHash);
processResolver({ reason });
}
});
// Acquire a worker for the configuration
worker = await this.getOrCreateWebWorker(configuration);
// Keep a promise that will resolve in the future when the
// underlying process terminates.
const onDidTerminate = new Promise<IOnDidTerminateSharedProcessWorkerProcess>(resolve => {
this.processResolvers.set(configurationHash, resolve);
});
if (cts.token.isCancellationRequested) {
return;
return onDidTerminate;
}
// Create a `MessageChannel` with 2 ports:
@ -78,7 +93,7 @@ export class SharedProcessWorkerService implements ISharedProcessWorkerService {
await worker.spawn(configuration, workerPort, cts.token);
if (cts.token.isCancellationRequested) {
return;
return onDidTerminate;
}
// We cannot just send the `MessagePort` through our protocol back
@ -86,6 +101,8 @@ export class SharedProcessWorkerService implements ISharedProcessWorkerService {
// to send it through the main process back to the window.
this.logService.trace(`SharedProcess: createWorker sending message port back to window (${workerLogId})`);
ipcRenderer.postMessage('vscode:relaySharedProcessWorkerMessageChannel', configuration, [windowPort]);
return onDidTerminate;
}
private getOrCreateWebWorker(configuration: ISharedProcessWorkerConfiguration): Promise<SharedProcessWebWorker> {
@ -103,11 +120,10 @@ export class SharedProcessWorkerService implements ISharedProcessWorkerService {
const sharedProcessWorker = new SharedProcessWebWorker(configuration.process.type, this.logService);
webWorkerPromise = sharedProcessWorker.init();
// Make sure to run through our normal
// `disposeWorker` call when the process
// terminates by itself.
sharedProcessWorker.onDidProcessSelfTerminate(configuration => {
this.disposeWorker(configuration);
// Make sure to run through our normal `disposeWorker` call
// when the process terminates by itself.
sharedProcessWorker.onDidProcessSelfTerminate(({ configuration, reason }) => {
this.doDisposeWorker(configuration, reason);
});
this.workers.set(configuration.process.moduleId, webWorkerPromise);
@ -117,18 +133,22 @@ export class SharedProcessWorkerService implements ISharedProcessWorkerService {
}
async disposeWorker(configuration: ISharedProcessWorkerConfiguration): Promise<void> {
const processDisposable = this.processes.get(hash(configuration));
return this.doDisposeWorker(configuration);
}
private doDisposeWorker(configuration: ISharedProcessWorkerConfiguration, reason?: ISharedProcessWorkerProcessExit): void {
const processDisposable = this.processeDisposables.get(hash(configuration));
if (processDisposable) {
this.logService.trace(`SharedProcess: disposeWorker (window: ${configuration.reply.windowId}, moduleId: ${configuration.process.moduleId})`);
processDisposable.dispose();
processDisposable(reason);
}
}
}
class SharedProcessWebWorker extends Disposable {
private readonly _onDidProcessSelfTerminate = this._register(new Emitter<ISharedProcessWorkerConfiguration>());
private readonly _onDidProcessSelfTerminate = this._register(new Emitter<{ configuration: ISharedProcessWorkerConfiguration, reason: ISharedProcessWorkerProcessExit }>());
readonly onDidProcessSelfTerminate = this._onDidProcessSelfTerminate.event;
private readonly workerReady: Promise<Worker> = this.doInit();
@ -186,8 +206,8 @@ class SharedProcessWebWorker extends Disposable {
// Lifecycle: self termination
case SharedProcessWorkerMessages.SelfTerminated:
if (configuration) {
this._onDidProcessSelfTerminate.fire(configuration);
if (configuration && message) {
this._onDidProcessSelfTerminate.fire({ configuration, reason: JSON.parse(message) });
}
break;

View file

@ -95,7 +95,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution
return; // no auto save for readonly or untitled editors
}
// Determine if we need to save all. In case of a window focus change we also save if
// Determine if we need to save all. In case of a window focus change we also save if
// auto save mode is configured to be ON_FOCUS_CHANGE (editor focus change)
const mode = this.filesConfigurationService.getAutoSaveMode();
if (
@ -115,7 +115,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution
private onAutoSaveConfigurationChange(config: IAutoSaveConfiguration, fromEvent: boolean): void {
// Update auto save after delay config
this.autoSaveAfterDelay = (typeof config.autoSaveDelay === 'number') && config.autoSaveDelay > 0 ? config.autoSaveDelay : undefined;
this.autoSaveAfterDelay = (typeof config.autoSaveDelay === 'number') && config.autoSaveDelay >= 0 ? config.autoSaveDelay : undefined;
// Trigger a save-all when auto save is enabled
if (fromEvent) {

View file

@ -1059,26 +1059,31 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
let openEditorPromise: Promise<IEditorPane | undefined>;
if (context.active) {
openEditorPromise = (async () => {
const result = await this.editorPane.openEditor(editor, options, { newInGroup: context.isNew });
const { pane, changed, cancelled, error } = await this.editorPane.openEditor(editor, options, { newInGroup: context.isNew });
// Return early if the operation was cancelled by another operation
if (cancelled) {
return undefined;
}
// Editor change event
if (result.editorChanged) {
if (changed) {
this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_ACTIVE, editor });
}
// Handle errors but do not bubble them up
if (result.error) {
await this.doHandleOpenEditorError(result.error, editor, options);
if (error) {
await this.doHandleOpenEditorError(error, editor, options);
}
// Without an editor pane, recover by closing the active editor
// (if the input is still the active one)
if (!result.editorPane && this.activeEditor === editor) {
if (!pane && this.activeEditor === editor) {
const focusNext = !options || !options.preserveFocus;
this.doCloseEditor(editor, focusNext, { fromError: true });
}
return result.editorPane;
return pane;
})();
} else {
openEditorPromise = Promise.resolve(undefined); // inactive: return undefined as result to signal this

View file

@ -12,7 +12,7 @@ import { IEditorPaneRegistry, IEditorPaneDescriptor } from 'vs/workbench/browser
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEditorProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress';
import { IEditorProgressService, IOperation, LongRunningOperation } from 'vs/platform/progress/common/progress';
import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
import { Emitter } from 'vs/base/common/event';
import { assertIsDefined } from 'vs/base/common/types';
@ -32,12 +32,12 @@ export interface IOpenEditorResult {
* open the editor and in cases where no placeholder is being
* used.
*/
readonly editorPane?: EditorPane;
readonly pane?: EditorPane;
/**
* Whether the editor changed as a result of opening.
*/
readonly editorChanged?: boolean;
readonly changed?: boolean;
/**
* This property is set when an editor fails to restore and
@ -45,6 +45,14 @@ export interface IOpenEditorResult {
* to still present the error to the user in that case.
*/
readonly error?: Error;
/**
* This property indicates whether the open editor operation was
* cancelled or not. The operation may have been cancelled
* in case another editor open operation was triggered right
* after cancelling this one out.
*/
readonly cancelled?: boolean;
}
export class EditorPanes extends Disposable {
@ -136,11 +144,32 @@ export class EditorPanes extends Disposable {
private async doOpenEditor(descriptor: IEditorPaneDescriptor, editor: EditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext = Object.create(null)): Promise<IOpenEditorResult> {
// Editor pane
const editorPane = this.doShowEditorPane(descriptor);
const pane = this.doShowEditorPane(descriptor);
// Show progress while setting input after a certain timeout.
// If the workbench is opening be more relaxed about progress
// showing by increasing the delay a little bit to reduce flicker.
const operation = this.editorOperation.start(this.layoutService.isRestored() ? 800 : 3200);
// Apply input to pane
const editorChanged = await this.doSetInput(editorPane, editor, options, context);
return { editorPane, editorChanged };
let changed: boolean;
let cancelled: boolean;
try {
changed = await this.doSetInput(pane, operation, editor, options, context);
cancelled = !operation.isCurrent();
} finally {
operation.stop();
}
// Focus unless cancelled
if (!cancelled) {
const focus = !options || !options.preserveFocus;
if (focus) {
pane.focus();
}
}
return { pane, changed, cancelled };
}
private getEditorPaneDescriptor(editor: EditorInput): IEditorPaneDescriptor {
@ -234,47 +263,22 @@ export class EditorPanes extends Disposable {
this._onDidChangeSizeConstraints.fire(undefined);
}
private async doSetInput(editorPane: EditorPane, editor: EditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext): Promise<boolean> {
private async doSetInput(editorPane: EditorPane, operation: IOperation, editor: EditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext): Promise<boolean> {
const forceReload = options?.forceReload;
const inputMatches = editorPane.input?.matches(editor);
// If the input did not change, return early and only apply the options
// unless the options instruct us to force open it even if it is the same
const forceReload = options?.forceReload;
const inputMatches = editorPane.input && editorPane.input.matches(editor);
if (inputMatches && !forceReload) {
// Forward options
editorPane.setOptions(options);
// Still focus as needed
const focus = !options || !options.preserveFocus;
if (focus) {
editorPane.focus();
}
return false;
}
// Show progress while setting input after a certain timeout. If the workbench is opening
// be more relaxed about progress showing by increasing the delay a little bit to reduce flicker.
const operation = this.editorOperation.start(this.layoutService.isRestored() ? 800 : 3200);
// Call into editor pane
const editorWillChange = !inputMatches;
try {
// Otherwise set the input to the editor pane
else {
await editorPane.setInput(editor, options, context, operation.token);
// Focus (unless prevented or another operation is running)
if (operation.isCurrent()) {
const focus = !options || !options.preserveFocus;
if (focus) {
editorPane.focus();
}
}
return editorWillChange;
} finally {
operation.stop();
}
return !inputMatches;
}
private doHideActiveEditorPane(): void {

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

@ -34,7 +34,6 @@ import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { IExplorerService } from 'vs/workbench/contrib/files/browser/files';
import { FileEditorInputSerializer, FileEditorWorkingCopyEditorHandler } from 'vs/workbench/contrib/files/browser/editors/fileEditorHandler';
import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry';
import product from 'vs/platform/product/common/product';
class FileUriLabelContribution implements IWorkbenchContribution {
@ -240,6 +239,7 @@ configurationRegistry.registerConfiguration({
'files.autoSaveDelay': {
'type': 'number',
'default': 1000,
'minimum': 0,
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSaveDelay' }, "Controls the delay in milliseconds after which an editor with unsaved changes is saved automatically. Only applies when `#files.autoSave#` is set to `{0}`.", AutoSaveConfiguration.AFTER_DELAY)
},
'files.watcherExclude': {
@ -267,7 +267,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."),
@ -311,7 +311,7 @@ configurationRegistry.registerConfiguration({
'files.experimentalSandboxedFileService': {
'type': 'boolean',
'description': nls.localize('files.experimentalSandboxedFileService', "Experimental: changes the file service to be sandboxed. Do not change this unless instructed!"),
'default': product.quality !== 'stable',
'default': true,
'scope': ConfigurationScope.APPLICATION
},
}

View file

@ -14,7 +14,6 @@ import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { SharedDesktopMain } from 'vs/workbench/electron-sandbox/shared.desktop.main';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services';
import { ISharedProcessWorkerWorkbenchService } from 'vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService';
import product from 'vs/platform/product/common/product';
class DesktopMain extends SharedDesktopMain {
@ -28,7 +27,7 @@ class DesktopMain extends SharedDesktopMain {
// Local Files
let diskFileSystemProvider: ElectronFileSystemProvider | SandboxedDiskFileSystemProvider;
if (this.configuration.experimentalSandboxedFileService ?? product.quality !== 'stable') {
if (this.configuration.experimentalSandboxedFileService !== false) {
logService.info('[FileService]: Using sandbox ready file system provider');
diskFileSystemProvider = this._register(new SandboxedDiskFileSystemProvider(mainProcessService, sharedProcessWorkerWorkbenchService, logService));
} else {

View file

@ -164,6 +164,38 @@ suite('EditorService', () => {
didCloseEditorListener.dispose();
});
test('openEditor() - multiple calls are cancelled and indicated as such', async () => {
const [, service] = await createEditorService();
let input = new TestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID);
let otherInput = new TestFileEditorInput(URI.parse('my://resource2-basics'), TEST_EDITOR_INPUT_ID);
let activeEditorChangeEventCounter = 0;
const activeEditorChangeListener = service.onDidActiveEditorChange(() => {
activeEditorChangeEventCounter++;
});
let visibleEditorChangeEventCounter = 0;
const visibleEditorChangeListener = service.onDidVisibleEditorsChange(() => {
visibleEditorChangeEventCounter++;
});
const editorP1 = service.openEditor(input, { pinned: true });
const editorP2 = service.openEditor(otherInput, { pinned: true });
const editor1 = await editorP1;
assert.strictEqual(editor1, undefined);
const editor2 = await editorP2;
assert.strictEqual(editor2?.input, otherInput);
assert.strictEqual(activeEditorChangeEventCounter, 1);
assert.strictEqual(visibleEditorChangeEventCounter, 1);
activeEditorChangeListener.dispose();
visibleEditorChangeListener.dispose();
});
test('openEditor() - locked groups', async () => {
disposables.add(registerTestFileEditor());

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { DisposableStore } from 'vs/base/common/lifecycle';
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { getDelayedChannel, ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { AbstractWatcherService, IDiskFileChange, ILogMessage, IWatcherService } from 'vs/platform/files/common/watcher';
import { ISharedProcessWorkerWorkbenchService } from 'vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService';
@ -22,13 +22,24 @@ export class ParcelFileWatcher extends AbstractWatcherService {
}
protected override createService(disposables: DisposableStore): IWatcherService {
return ProxyChannel.toService<IWatcherService>(getDelayedChannel((async () => {
// Acquire parcel watcher via shared process worker
const watcherChannel = this.sharedProcessWorkerWorkbenchService.createWorkerChannel({
moduleId: 'vs/platform/files/node/watcher/parcel/watcherApp',
type: 'watcherServiceParcelSharedProcess'
}, 'watcher').channel;
// Acquire parcel watcher via shared process worker
const { client, onDidTerminate } = await this.sharedProcessWorkerWorkbenchService.createWorker({
moduleId: 'vs/platform/files/node/watcher/parcel/watcherApp',
type: 'watcherServiceParcelSharedProcess'
});
return ProxyChannel.toService<IWatcherService>(watcherChannel);
// React on unexpected termination of the watcher process
// We never expect the watcher to terminate by its own,
// so if that happens we want to restart the watcher.
onDidTerminate.then(({ reason }) => {
if (reason) {
this.onError(`terminated by itself with code ${reason.code}, signal: ${reason.signal}`);
}
});
return client.getChannel('watcher');
})()));
}
}

View file

@ -169,7 +169,7 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi
return AutoSaveMode.ON_WINDOW_CHANGE;
}
if (this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0) {
if (typeof this.configuredAutoSaveDelay === 'number' && this.configuredAutoSaveDelay >= 0) {
return this.configuredAutoSaveDelay <= 1000 ? AutoSaveMode.AFTER_SHORT_DELAY : AutoSaveMode.AFTER_LONG_DELAY;
}
@ -178,7 +178,7 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi
getAutoSaveConfiguration(): IAutoSaveConfiguration {
return {
autoSaveDelay: this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0 ? this.configuredAutoSaveDelay : undefined,
autoSaveDelay: typeof this.configuredAutoSaveDelay === 'number' && this.configuredAutoSaveDelay >= 0 ? this.configuredAutoSaveDelay : undefined,
autoSaveFocusChange: !!this.configuredAutoSaveOnFocusChange,
autoSaveApplicationChange: !!this.configuredAutoSaveOnWindowChange
};

View file

@ -9,7 +9,7 @@ import { AbstractLifecycleService } from 'vs/workbench/services/lifecycle/common
import { localize } from 'vs/nls';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IDisposable } from 'vs/base/common/lifecycle';
import { addDisposableListener } from 'vs/base/browser/dom';
import { addDisposableListener, EventType } from 'vs/base/browser/dom';
import { IStorageService } from 'vs/platform/storage/common/storage';
export class BrowserLifecycleService extends AbstractLifecycleService {
@ -29,7 +29,7 @@ export class BrowserLifecycleService extends AbstractLifecycleService {
private registerListeners(): void {
// beforeUnload
this.beforeUnloadDisposable = addDisposableListener(window, 'beforeunload', (e: BeforeUnloadEvent) => this.onBeforeUnload(e));
this.beforeUnloadDisposable = addDisposableListener(window, EventType.BEFORE_UNLOAD, (e: BeforeUnloadEvent) => this.onBeforeUnload(e));
}
private onBeforeUnload(event: BeforeUnloadEvent): void {

View file

@ -8,16 +8,30 @@ import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/
import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services';
import { Client as MessagePortClient } from 'vs/base/parts/ipc/common/ipc.mp';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ipcSharedProcessWorkerChannelName, ISharedProcessWorkerProcess, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
import { getDelayedChannel, IChannel, ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { IOnDidTerminateSharedProcessWorkerProcess, ipcSharedProcessWorkerChannelName, ISharedProcessWorkerProcess, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
import { IPCClient, ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { generateUuid } from 'vs/base/common/uuid';
import { acquirePort } from 'vs/base/parts/ipc/electron-sandbox/ipc.mp';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
export const ISharedProcessWorkerWorkbenchService = createDecorator<ISharedProcessWorkerWorkbenchService>('sharedProcessWorkerWorkbenchService');
export interface IWorkerChannel extends IDisposable {
channel: IChannel;
export interface ISharedProcessWorker extends IDisposable {
/**
* A IPC client to communicate to the worker process.
*/
client: IPCClient<string>;
/**
* A promise that resolves to an object once the
* worker process terminates, giving information
* how the process terminated.
*
* This can be used to figure out whether the worker
* should be restarted in case of an unexpected
* termination.
*/
onDidTerminate: Promise<IOnDidTerminateSharedProcessWorkerProcess>;
}
export interface ISharedProcessWorkerWorkbenchService {
@ -41,13 +55,12 @@ export interface ISharedProcessWorkerWorkbenchService {
* window and the communication channel allows to dynamically update the processes
* after the fact.
*
* @param process information around the process to fork
* @param channelName the name of the channel the process will respond to
* @param process information around the process to fork as worker
*
* @returns the worker channel to communicate with. Provides a `dispose` method that
* @returns the worker IPC client to communicate with. Provides a `dispose` method that
* allows to terminate the worker if needed.
*/
createWorkerChannel(process: ISharedProcessWorkerProcess, channelName: string): IWorkerChannel;
createWorker(process: ISharedProcessWorkerProcess): Promise<ISharedProcessWorker>;
}
export class SharedProcessWorkerWorkbenchService extends Disposable implements ISharedProcessWorkerWorkbenchService {
@ -71,21 +84,9 @@ export class SharedProcessWorkerWorkbenchService extends Disposable implements I
super();
}
createWorkerChannel(process: ISharedProcessWorkerProcess, channelName: string): IWorkerChannel {
const cts = new CancellationTokenSource();
async createWorker(process: ISharedProcessWorkerProcess): Promise<ISharedProcessWorker> {
this.logService.trace('Renderer->SharedProcess#createWorker');
return {
channel: getDelayedChannel(this.doCreateWorkerChannel(process, channelName, cts.token)),
dispose: () => cts.dispose(true)
};
}
private async doCreateWorkerChannel(process: ISharedProcessWorkerProcess, channelName: string, token: CancellationToken): Promise<IChannel> {
this.logService.trace('Renderer->SharedProcess#createWorkerChannel');
// Dispose when cancelled
const disposables = new DisposableStore();
token.onCancellationRequested(() => disposables.dispose());
// Get ready to acquire the message port from the shared process worker
const nonce = generateUuid();
@ -94,12 +95,13 @@ export class SharedProcessWorkerWorkbenchService extends Disposable implements I
// Actually talk with the shared process service
// to create a new process from a worker
this.sharedProcessWorkerService.createWorker({
const onDidTerminate = this.sharedProcessWorkerService.createWorker({
process,
reply: { windowId: this.windowId, channel: responseChannel, nonce }
});
// Dispose worker upon disposal via shared process service
const disposables = new DisposableStore();
disposables.add(toDisposable(() => {
this.logService.trace('Renderer->SharedProcess#disposeWorker', process);
@ -110,11 +112,9 @@ export class SharedProcessWorkerWorkbenchService extends Disposable implements I
}));
const port = await portPromise;
const client = disposables.add(new MessagePortClient(port, `window:${this.windowId},module:${process.moduleId}`));
this.logService.trace('Renderer->SharedProcess#createWorkerChannel: connection established');
const client = disposables.add(new MessagePortClient(port, `window:${this.windowId},module:${process.moduleId}`));
return client.getChannel(channelName);
return { client, onDidTerminate, dispose: () => disposables.dispose() };
}
}

View file

@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* 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 terminalEncoding from 'vs/base/node/terminalEncoding';
suite('Encoding', () => {
test('resolve terminal encoding (detect)', async function () {
const enc = await terminalEncoding.resolveTerminalEncoding();
assert.ok(enc.length > 0);
});
});

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"
@ -4678,16 +4651,21 @@ got@^9.6.0:
to-readable-stream "^1.0.0"
url-parse-lax "^3.0.0"
graceful-fs@4.2.6, graceful-fs@^4.1.2, graceful-fs@^4.2.4:
version "4.2.6"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
graceful-fs@4.2.8:
version "4.2.8"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
graceful-fs@^4.1.2, graceful-fs@^4.2.4:
version "4.2.6"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
growl@1.10.5:
version "1.10.5"
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
@ -8400,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"