commit
9375113a83
|
@ -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']
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
112
remote/yarn.lock
112
remote/yarn.lock
|
@ -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"
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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] });
|
||||
});
|
||||
});
|
|
@ -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>;
|
||||
}
|
|
@ -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));
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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>());
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'),
|
||||
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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');
|
||||
})()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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() };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
47
yarn.lock
47
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue