diff --git a/src/vs/platform/extensionManagement/node/extensionsWatcher.ts b/src/vs/platform/extensionManagement/node/extensionsWatcher.ts index 06d6b23b04a..9f2ca6fd922 100644 --- a/src/vs/platform/extensionManagement/node/extensionsWatcher.ts +++ b/src/vs/platform/extensionManagement/node/extensionsWatcher.ts @@ -41,7 +41,7 @@ export class ExtensionsWatcher extends Disposable { const extensionsResource = URI.file(environmentService.extensionsPath); const extUri = new ExtUri(resource => !fileService.hasCapability(resource, FileSystemProviderCapabilities.PathCaseSensitive)); this._register(fileService.watch(extensionsResource)); - this._register(Event.filter(fileService.onDidFilesChange, e => e.raw.some(change => this.doesChangeAffects(change, extensionsResource, extUri)))(() => this.onDidChange())); + this._register(Event.filter(fileService.onDidChangeFilesRaw, raw => raw.some(change => this.doesChangeAffects(change, extensionsResource, extUri)))(() => this.onDidChange())); } private doesChangeAffects(change: IFileChange, extensionsResource: URI, extUri: ExtUri): boolean { diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index ed720fe757d..8ff554a4013 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -982,6 +982,9 @@ export class FileService extends Disposable implements IFileService { private readonly _onDidFilesChange = this._register(new Emitter()); readonly onDidFilesChange = this._onDidFilesChange.event; + private readonly _onDidChangeFilesRaw = this._register(new Emitter()); + readonly onDidChangeFilesRaw = this._onDidChangeFilesRaw.event; + private readonly activeWatchers = new Map(); private readonly caseSensitiveFileEventsWorker = this._register( @@ -1003,15 +1006,24 @@ export class FileService extends Disposable implements IFileService { ); private onDidChangeFile(changes: readonly IFileChange[], caseSensitive: boolean): void { - const worker = caseSensitive ? this.caseSensitiveFileEventsWorker : this.caseInsensitiveFileEventsWorker; - const worked = worker.work(changes); - if (!worked && FileService.FILE_EVENTS_THROTTLING.warningscounter++ < 10) { - this.logService.warn(`[File watcher]: started ignoring events due to too many file change events at once (incoming: ${changes.length}, most recent change: ${changes[0].resource.toString()}). Use 'files.watcherExclude' setting to exclude folders with lots of changing files (e.g. compilation output).`); + // Event #1: access to raw events + { + this._onDidChangeFilesRaw.fire(changes); } - if (worker.pending > 0) { - this.logService.trace(`[File watcher]: started throttling events due to large amount of file change events at once (pending: ${worker.pending}, most recent change: ${changes[0].resource.toString()}). Use 'files.watcherExclude' setting to exclude folders with lots of changing files (e.g. compilation output).`); + // Event #2: throttled due to performance reasons + { + const worker = caseSensitive ? this.caseSensitiveFileEventsWorker : this.caseInsensitiveFileEventsWorker; + const worked = worker.work(changes); + + if (!worked && FileService.FILE_EVENTS_THROTTLING.warningscounter++ < 10) { + this.logService.warn(`[File watcher]: started ignoring events due to too many file change events at once (incoming: ${changes.length}, most recent change: ${changes[0].resource.toString()}). Use 'files.watcherExclude' setting to exclude folders with lots of changing files (e.g. compilation output).`); + } + + if (worker.pending > 0) { + this.logService.trace(`[File watcher]: started throttling events due to large amount of file change events at once (pending: ${worker.pending}, most recent change: ${changes[0].resource.toString()}). Use 'files.watcherExclude' setting to exclude folders with lots of changing files (e.g. compilation output).`); + } } } diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index d0161b454b6..bad6dd986c0 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -77,6 +77,15 @@ export interface IFileService { */ readonly onDidFilesChange: Event; + /** + * + * Raw access to all file events emitted from file system providers. + * + * @deprecated use this method only if you know what you are doing. use the other watch related events + * and APIs for more efficient file watching. + */ + readonly onDidChangeFilesRaw: Event; + /** * An event that is fired upon successful completion of a certain file operation. */ @@ -645,7 +654,7 @@ export class FileChangesEvent { private readonly updated: TernarySearchTree | undefined = undefined; private readonly deleted: TernarySearchTree | undefined = undefined; - constructor(private readonly changes: readonly IFileChange[], ignorePathCasing: boolean) { + constructor(changes: readonly IFileChange[], ignorePathCasing: boolean) { for (const change of changes) { switch (change.type) { case FileChangeType.ADDED: @@ -752,14 +761,6 @@ export class FileChangesEvent { return !!this.updated; } - /** - * @deprecated use the `contains` or `affects` method to efficiently find - * out if the event relates to a given resource. these methods ensure: - * - that there is no expensive lookup needed (by using a `TernarySearchTree`) - * - correctly handles `FileChangeType.DELETED` events - */ - get raw(): readonly IFileChange[] { return this.changes; } - /** * @deprecated use the `contains` or `affects` method to efficiently find * out if the event relates to a given resource. these methods ensure: diff --git a/src/vs/platform/files/test/common/files.test.ts b/src/vs/platform/files/test/common/files.test.ts index 41f95573eca..aa537add587 100644 --- a/src/vs/platform/files/test/common/files.test.ts +++ b/src/vs/platform/files/test/common/files.test.ts @@ -70,7 +70,6 @@ suite('Files', () => { } assert(!event.contains(toResource.call(this, '/bar/folder2/somefile'), FileChangeType.DELETED)); - assert.strictEqual(6, event.raw.length); assert.strictEqual(1, count(event.rawAdded)); assert.strictEqual(true, event.gotAdded()); assert.strictEqual(true, event.gotUpdated()); diff --git a/src/vs/platform/files/test/electron-browser/diskFileService.test.ts b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts index 03920cb53e4..54ba93f78db 100644 --- a/src/vs/platform/files/test/electron-browser/diskFileService.test.ts +++ b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts @@ -13,7 +13,7 @@ import { join, basename, dirname, posix } from 'vs/base/common/path'; import { Promises, rimrafSync } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync, createReadStream } from 'fs'; -import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileChangesEvent, FileOperationError, etag, IStat, IFileStatWithMetadata, IReadFileOptions, FilePermission, NotModifiedSinceFileOperationError } from 'vs/platform/files/common/files'; +import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileOperationError, etag, IStat, IFileStatWithMetadata, IReadFileOptions, FilePermission, NotModifiedSinceFileOperationError } from 'vs/platform/files/common/files'; import { NullLogService } from 'vs/platform/log/common/log'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -2279,23 +2279,23 @@ flakySuite('Disk File Service', function () { } } - function printEvents(event: FileChangesEvent): string { - return event.raw.map(change => `Change: type ${toString(change.type)} path ${change.resource.toString()}`).join('\n'); + function printEvents(raw: readonly IFileChange[]): string { + return raw.map(change => `Change: type ${toString(change.type)} path ${change.resource.toString()}`).join('\n'); } - const listenerDisposable = service.onDidFilesChange(event => { + const listenerDisposable = service.onDidChangeFilesRaw(raw => { watcherDisposable.dispose(); listenerDisposable.dispose(); try { - assert.strictEqual(event.raw.length, expected.length, `Expected ${expected.length} events, but got ${event.raw.length}. Details (${printEvents(event)})`); + assert.strictEqual(raw.length, expected.length, `Expected ${expected.length} events, but got ${raw.length}. Details (${printEvents(raw)})`); if (expected.length === 1) { - assert.strictEqual(event.raw[0].type, expected[0][0], `Expected ${toString(expected[0][0])} but got ${toString(event.raw[0].type)}. Details (${printEvents(event)})`); - assert.strictEqual(event.raw[0].resource.fsPath, expected[0][1].fsPath); + assert.strictEqual(raw[0].type, expected[0][0], `Expected ${toString(expected[0][0])} but got ${toString(raw[0].type)}. Details (${printEvents(raw)})`); + assert.strictEqual(raw[0].resource.fsPath, expected[0][1].fsPath); } else { for (const expect of expected) { - assert.strictEqual(hasChange(event.raw, expect[0], expect[1]), true, `Unable to find ${toString(expect[0])} for ${expect[1].fsPath}. Details (${printEvents(event)})`); + assert.strictEqual(hasChange(raw, expect[0], expect[1]), true, `Unable to find ${toString(expect[0])} for ${expect[1].fsPath}. Details (${printEvents(raw)})`); } } diff --git a/src/vs/platform/files/test/electron-browser/normalizer.test.ts b/src/vs/platform/files/test/electron-browser/normalizer.test.ts index 745f3cf1999..bbaecf37888 100644 --- a/src/vs/platform/files/test/electron-browser/normalizer.test.ts +++ b/src/vs/platform/files/test/electron-browser/normalizer.test.ts @@ -4,24 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as platform from 'vs/base/common/platform'; -import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; +import { isWindows, isLinux } from 'vs/base/common/platform'; +import { FileChangesEvent, FileChangeType, IFileChange } from 'vs/platform/files/common/files'; import { URI as uri } from 'vs/base/common/uri'; import { IDiskFileChange, normalizeFileChanges, toFileChanges } from 'vs/platform/files/node/watcher/watcher'; import { Event, Emitter } from 'vs/base/common/event'; function toFileChangesEvent(changes: IDiskFileChange[]): FileChangesEvent { - return new FileChangesEvent(toFileChanges(changes), !platform.isLinux); + return new FileChangesEvent(toFileChanges(changes), !isLinux); } class TestFileWatcher { - private readonly _onDidFilesChange: Emitter; + private readonly _onDidFilesChange: Emitter<{ raw: IFileChange[], event: FileChangesEvent }>; constructor() { - this._onDidFilesChange = new Emitter(); + this._onDidFilesChange = new Emitter<{ raw: IFileChange[], event: FileChangesEvent }>(); } - get onDidFilesChange(): Event { + get onDidFilesChange(): Event<{ raw: IFileChange[], event: FileChangesEvent }> { return this._onDidFilesChange.event; } @@ -36,7 +36,7 @@ class TestFileWatcher { // Emit through event emitter if (normalizedEvents.length > 0) { - this._onDidFilesChange.fire(toFileChangesEvent(normalizedEvents)); + this._onDidFilesChange.fire({ raw: toFileChanges(normalizedEvents), event: toFileChangesEvent(normalizedEvents) }); } } } @@ -62,9 +62,9 @@ suite('Normalizer', () => { { path: deleted.fsPath, type: FileChangeType.DELETED }, ]; - watch.onDidFilesChange(e => { + watch.onDidFilesChange(({ event: e, raw }) => { assert.ok(e); - assert.strictEqual(e.raw.length, 3); + assert.strictEqual(raw.length, 3); assert.ok(e.contains(added, FileChangeType.ADDED)); assert.ok(e.contains(updated, FileChangeType.UPDATED)); assert.ok(e.contains(deleted, FileChangeType.DELETED)); @@ -75,7 +75,7 @@ suite('Normalizer', () => { watch.report(raw); }); - let pathSpecs = platform.isWindows ? [Path.WINDOWS, Path.UNC] : [Path.UNIX]; + let pathSpecs = isWindows ? [Path.WINDOWS, Path.UNC] : [Path.UNIX]; pathSpecs.forEach((p) => { test('delete only reported for top level folder (' + p + ')', function (done: () => void) { const watch = new TestFileWatcher(); @@ -101,9 +101,9 @@ suite('Normalizer', () => { { path: updatedFile.fsPath, type: FileChangeType.UPDATED } ]; - watch.onDidFilesChange(e => { + watch.onDidFilesChange(({ event: e, raw }) => { assert.ok(e); - assert.strictEqual(e.raw.length, 5); + assert.strictEqual(raw.length, 5); assert.ok(e.contains(deletedFolderA, FileChangeType.DELETED)); assert.ok(e.contains(deletedFolderB, FileChangeType.DELETED)); @@ -131,9 +131,9 @@ suite('Normalizer', () => { { path: unrelated.fsPath, type: FileChangeType.UPDATED }, ]; - watch.onDidFilesChange(e => { + watch.onDidFilesChange(({ event: e, raw }) => { assert.ok(e); - assert.strictEqual(e.raw.length, 1); + assert.strictEqual(raw.length, 1); assert.ok(e.contains(unrelated, FileChangeType.UPDATED)); @@ -156,9 +156,9 @@ suite('Normalizer', () => { { path: unrelated.fsPath, type: FileChangeType.UPDATED }, ]; - watch.onDidFilesChange(e => { + watch.onDidFilesChange(({ event: e, raw }) => { assert.ok(e); - assert.strictEqual(e.raw.length, 2); + assert.strictEqual(raw.length, 2); assert.ok(e.contains(deleted, FileChangeType.UPDATED)); assert.ok(e.contains(unrelated, FileChangeType.UPDATED)); @@ -182,9 +182,9 @@ suite('Normalizer', () => { { path: unrelated.fsPath, type: FileChangeType.UPDATED }, ]; - watch.onDidFilesChange(e => { + watch.onDidFilesChange(({ event: e, raw }) => { assert.ok(e); - assert.strictEqual(e.raw.length, 2); + assert.strictEqual(raw.length, 2); assert.ok(e.contains(created, FileChangeType.ADDED)); assert.ok(!e.contains(created, FileChangeType.UPDATED)); @@ -211,9 +211,9 @@ suite('Normalizer', () => { { path: updated.fsPath, type: FileChangeType.DELETED } ]; - watch.onDidFilesChange(e => { + watch.onDidFilesChange(({ event: e, raw }) => { assert.ok(e); - assert.strictEqual(e.raw.length, 2); + assert.strictEqual(raw.length, 2); assert.ok(e.contains(deleted, FileChangeType.DELETED)); assert.ok(!e.contains(updated, FileChangeType.UPDATED)); diff --git a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts index e7b99badc0e..f9c8758c1be 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts @@ -51,8 +51,8 @@ export class MainThreadFileSystemEventService { changed: [], deleted: [] }; - this._listener.add(fileService.onDidFilesChange(event => { - for (let change of event.raw) { + this._listener.add(fileService.onDidChangeFilesRaw(changes => { + for (let change of changes) { switch (change.type) { case FileChangeType.ADDED: events.created.push(change.resource); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index f37d34b28b2..64288574238 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -836,6 +836,9 @@ export class TestFileService implements IFileService { get onDidFilesChange(): Event { return this._onDidFilesChange.event; } fireFileChanges(event: FileChangesEvent): void { this._onDidFilesChange.fire(event); } + private readonly _onDidChangeFilesRaw = new Emitter(); + get onDidChangeFilesRaw(): Event { return this._onDidChangeFilesRaw.event; } + private readonly _onDidRunOperation = new Emitter(); get onDidRunOperation(): Event { return this._onDidRunOperation.event; } fireAfterOperation(event: FileOperationEvent): void { this._onDidRunOperation.fire(event); }