Skip to content

Commit d200d08

Browse files
authored
Merge branch 'main' into fix/git-bash-split
2 parents 951a21a + 97702b8 commit d200d08

File tree

12 files changed

+253
-61
lines changed

12 files changed

+253
-61
lines changed

src/vs/platform/files/common/diskFileSystemProvider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export abstract class AbstractDiskFileSystemProvider extends Disposable implemen
7272
private watchUniversal(resource: URI, opts: IWatchOptions): IDisposable {
7373

7474
// Add to list of paths to watch universally
75-
const pathToWatch: IUniversalWatchRequest = { path: this.toFilePath(resource), excludes: opts.excludes, includes: opts.includes, recursive: opts.recursive, correlationId: opts.correlationId };
75+
const pathToWatch: IUniversalWatchRequest = { path: this.toFilePath(resource), excludes: opts.excludes, includes: opts.includes, recursive: opts.recursive, filter: opts.filter, correlationId: opts.correlationId };
7676
const remove = insert(this.universalPathsToWatch, pathToWatch);
7777

7878
// Trigger update
@@ -153,7 +153,7 @@ export abstract class AbstractDiskFileSystemProvider extends Disposable implemen
153153
private watchNonRecursive(resource: URI, opts: IWatchOptions): IDisposable {
154154

155155
// Add to list of paths to watch non-recursively
156-
const pathToWatch: INonRecursiveWatchRequest = { path: this.toFilePath(resource), excludes: opts.excludes, includes: opts.includes, recursive: false, correlationId: opts.correlationId };
156+
const pathToWatch: INonRecursiveWatchRequest = { path: this.toFilePath(resource), excludes: opts.excludes, includes: opts.includes, recursive: false, filter: opts.filter, correlationId: opts.correlationId };
157157
const remove = insert(this.nonRecursivePathsToWatch, pathToWatch);
158158

159159
// Trigger update

src/vs/platform/files/common/files.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,15 @@ export interface IWatchOptionsWithoutCorrelation {
519519
* always matched relative to the watched folder.
520520
*/
521521
includes?: Array<string | IRelativePattern>;
522+
523+
/**
524+
* If provided, allows to filter the events that the watcher should consider
525+
* for emitting. If not provided, all events are emitted.
526+
*
527+
* For example, to emit added and updated events, set to:
528+
* `FileChangeFilter.ADDED | FileChangeFilter.UPDATED`.
529+
*/
530+
filter?: FileChangeFilter;
522531
}
523532

524533
export interface IWatchOptions extends IWatchOptionsWithoutCorrelation {
@@ -531,6 +540,12 @@ export interface IWatchOptions extends IWatchOptionsWithoutCorrelation {
531540
readonly correlationId?: number;
532541
}
533542

543+
export const enum FileChangeFilter {
544+
UPDATED = 1 << 1,
545+
ADDED = 1 << 2,
546+
DELETED = 1 << 3
547+
}
548+
534549
export interface IWatchOptionsWithCorrelation extends IWatchOptions {
535550
readonly correlationId: number;
536551
}

src/vs/platform/files/common/watcher.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/
99
import { isAbsolute } from 'vs/base/common/path';
1010
import { isLinux } from 'vs/base/common/platform';
1111
import { URI } from 'vs/base/common/uri';
12-
import { FileChangeType, IFileChange, isParent } from 'vs/platform/files/common/files';
12+
import { FileChangeFilter, FileChangeType, IFileChange, isParent } from 'vs/platform/files/common/files';
1313

1414
interface IWatchRequest {
1515

@@ -41,6 +41,15 @@ interface IWatchRequest {
4141
* id.
4242
*/
4343
readonly correlationId?: number;
44+
45+
/**
46+
* If provided, allows to filter the events that the watcher should consider
47+
* for emitting. If not provided, all events are emitted.
48+
*
49+
* For example, to emit added and updated events, set to:
50+
* `FileChangeFilter.ADDED | FileChangeFilter.UPDATED`.
51+
*/
52+
readonly filter?: FileChangeFilter;
4453
}
4554

4655
export interface IWatchRequestWithCorrelation extends IWatchRequest {
@@ -428,3 +437,41 @@ class EventCoalescer {
428437
}).concat(addOrChangeEvents);
429438
}
430439
}
440+
441+
export function isFiltered(event: IFileChange, filter: FileChangeFilter | undefined): boolean {
442+
if (typeof filter === 'number') {
443+
switch (event.type) {
444+
case FileChangeType.ADDED:
445+
return (filter & FileChangeFilter.ADDED) === 0;
446+
case FileChangeType.DELETED:
447+
return (filter & FileChangeFilter.DELETED) === 0;
448+
case FileChangeType.UPDATED:
449+
return (filter & FileChangeFilter.UPDATED) === 0;
450+
}
451+
}
452+
453+
return false;
454+
}
455+
456+
export function requestFilterToString(filter: FileChangeFilter | undefined): string {
457+
if (typeof filter === 'number') {
458+
const filters = [];
459+
if (filter & FileChangeFilter.ADDED) {
460+
filters.push('Added');
461+
}
462+
if (filter & FileChangeFilter.DELETED) {
463+
filters.push('Deleted');
464+
}
465+
if (filter & FileChangeFilter.UPDATED) {
466+
filters.push('Updated');
467+
}
468+
469+
if (filters.length === 0) {
470+
return '<all>';
471+
}
472+
473+
return filters.join(', ');
474+
}
475+
476+
return '<none>';
477+
}

src/vs/platform/files/node/watcher/baseWatcher.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { watchFile, unwatchFile, Stats } from 'fs';
77
import { Disposable, DisposableMap, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
8-
import { ILogMessage, IRecursiveWatcherWithSubscribe, IUniversalWatchRequest, IWatchRequestWithCorrelation, IWatcher, isWatchRequestWithCorrelation } from 'vs/platform/files/common/watcher';
8+
import { ILogMessage, IRecursiveWatcherWithSubscribe, IUniversalWatchRequest, IWatchRequestWithCorrelation, IWatcher, isWatchRequestWithCorrelation, requestFilterToString } from 'vs/platform/files/common/watcher';
99
import { Emitter, Event } from 'vs/base/common/event';
1010
import { FileChangeType, IFileChange } from 'vs/platform/files/common/files';
1111
import { URI } from 'vs/base/common/uri';
@@ -236,7 +236,7 @@ export abstract class BaseWatcher extends Disposable implements IWatcher {
236236
}
237237

238238
protected requestToString(request: IUniversalWatchRequest): string {
239-
return `${request.path} (excludes: ${request.excludes.length > 0 ? request.excludes : '<none>'}, includes: ${request.includes && request.includes.length > 0 ? JSON.stringify(request.includes) : '<all>'}, correlationId: ${typeof request.correlationId === 'number' ? request.correlationId : '<none>'})`;
239+
return `${request.path} (excludes: ${request.excludes.length > 0 ? request.excludes : '<none>'}, includes: ${request.includes && request.includes.length > 0 ? JSON.stringify(request.includes) : '<all>'}, filter: ${requestFilterToString(request.filter)}, correlationId: ${typeof request.correlationId === 'number' ? request.correlationId : '<none>'})`;
240240
}
241241

242242
protected abstract doWatch(requests: IUniversalWatchRequest[]): Promise<void>;

src/vs/platform/files/node/watcher/nodejs/nodejsWatcherLib.ts

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { URI } from 'vs/base/common/uri';
1616
import { realcase } from 'vs/base/node/extpath';
1717
import { Promises } from 'vs/base/node/pfs';
1818
import { FileChangeType, IFileChange } from 'vs/platform/files/common/files';
19-
import { ILogMessage, coalesceEvents, INonRecursiveWatchRequest, parseWatcherPatterns, IRecursiveWatcherWithSubscribe } from 'vs/platform/files/common/watcher';
19+
import { ILogMessage, coalesceEvents, INonRecursiveWatchRequest, parseWatcherPatterns, IRecursiveWatcherWithSubscribe, isFiltered, isWatchRequestWithCorrelation } from 'vs/platform/files/common/watcher';
2020

2121
export class NodeJSFileWatcherLibrary extends Disposable {
2222

@@ -51,6 +51,7 @@ export class NodeJSFileWatcherLibrary extends Disposable {
5151

5252
private readonly excludes = parseWatcherPatterns(this.request.path, this.request.excludes);
5353
private readonly includes = this.request.includes ? parseWatcherPatterns(this.request.path, this.request.includes) : undefined;
54+
private readonly filter = isWatchRequestWithCorrelation(this.request) ? this.request.filter : undefined; // TODO@bpasero filtering for now is only enabled when correlating because watchers are otherwise potentially reused
5455

5556
private readonly cts = new CancellationTokenSource();
5657

@@ -168,7 +169,7 @@ export class NodeJSFileWatcherLibrary extends Disposable {
168169
// so that the client can correlate the event with the request
169170
// properly. Without correlation, we do not have to do that
170171
// because the event will appear on the global listener already.
171-
this.onDidFilesChange([{ resource, type: change.type, cId: this.request.correlationId }]);
172+
this.onFileChange({ resource, type: change.type, cId: this.request.correlationId }, true /* skip excludes/includes (file is explicitly watched) */);
172173
}
173174
}
174175
});
@@ -467,25 +468,41 @@ export class NodeJSFileWatcherLibrary extends Disposable {
467468

468469
// Coalesce events: merge events of same kind
469470
const coalescedFileChanges = coalesceEvents(fileChanges);
470-
if (coalescedFileChanges.length > 0) {
471471

472-
// Logging
473-
if (this.verboseLogging) {
474-
for (const event of coalescedFileChanges) {
475-
this.trace(` >> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.resource.fsPath}`);
472+
// Filter events: based on request filter property
473+
const filteredEvents: IFileChange[] = [];
474+
for (const event of coalescedFileChanges) {
475+
if (isFiltered(event, this.filter)) {
476+
if (this.verboseLogging) {
477+
this.trace(` >> ignored (filtered) ${event.resource.fsPath}`);
476478
}
479+
480+
continue;
477481
}
478482

479-
// Broadcast to clients via throttled emitter
480-
const worked = this.throttledFileChangesEmitter.work(coalescedFileChanges);
483+
filteredEvents.push(event);
484+
}
481485

482-
// Logging
483-
if (!worked) {
484-
this.warn(`started ignoring events due to too many file change events at once (incoming: ${coalescedFileChanges.length}, most recent change: ${coalescedFileChanges[0].resource.fsPath}). Use 'files.watcherExclude' setting to exclude folders with lots of changing files (e.g. compilation output).`);
485-
} else {
486-
if (this.throttledFileChangesEmitter.pending > 0) {
487-
this.trace(`started throttling events due to large amount of file change events at once (pending: ${this.throttledFileChangesEmitter.pending}, most recent change: ${coalescedFileChanges[0].resource.fsPath}). Use 'files.watcherExclude' setting to exclude folders with lots of changing files (e.g. compilation output).`);
488-
}
486+
if (filteredEvents.length === 0) {
487+
return;
488+
}
489+
490+
// Logging
491+
if (this.verboseLogging) {
492+
for (const event of filteredEvents) {
493+
this.trace(` >> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.resource.fsPath}`);
494+
}
495+
}
496+
497+
// Broadcast to clients via throttled emitter
498+
const worked = this.throttledFileChangesEmitter.work(filteredEvents);
499+
500+
// Logging
501+
if (!worked) {
502+
this.warn(`started ignoring events due to too many file change events at once (incoming: ${filteredEvents.length}, most recent change: ${filteredEvents[0].resource.fsPath}). Use 'files.watcherExclude' setting to exclude folders with lots of changing files (e.g. compilation output).`);
503+
} else {
504+
if (this.throttledFileChangesEmitter.pending > 0) {
505+
this.trace(`started throttling events due to large amount of file change events at once (pending: ${this.throttledFileChangesEmitter.pending}, most recent change: ${filteredEvents[0].resource.fsPath}). Use 'files.watcherExclude' setting to exclude folders with lots of changing files (e.g. compilation output).`);
489506
}
490507
}
491508
}

src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
2121
import { realcaseSync, realpathSync } from 'vs/base/node/extpath';
2222
import { NodeJSFileWatcherLibrary } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcherLib';
2323
import { FileChangeType, IFileChange } from 'vs/platform/files/common/files';
24-
import { coalesceEvents, IRecursiveWatchRequest, parseWatcherPatterns, IRecursiveWatcherWithSubscribe } from 'vs/platform/files/common/watcher';
24+
import { coalesceEvents, IRecursiveWatchRequest, parseWatcherPatterns, IRecursiveWatcherWithSubscribe, isFiltered } from 'vs/platform/files/common/watcher';
2525
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
2626

2727
export class ParcelWatcherInstance extends Disposable {
@@ -236,7 +236,7 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS
236236
for (const watcher of this.watchers) {
237237

238238
// Requests or watchers with correlation always match on that
239-
if (typeof request.correlationId === 'number' || typeof watcher.request.correlationId === 'number') {
239+
if (this.isCorrelated(request) || this.isCorrelated(watcher.request)) {
240240
if (watcher.request.correlationId === request.correlationId) {
241241
return watcher;
242242
}
@@ -445,19 +445,6 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS
445445
return;
446446
}
447447

448-
for (const event of events) {
449-
450-
// Emit to instance subscriptions if any
451-
if (watcher.subscriptionsCount > 0) {
452-
watcher.notifyFileChange(event.resource.fsPath, event);
453-
}
454-
455-
// Logging
456-
if (this.verboseLogging) {
457-
this.traceEvent(event, watcher.request);
458-
}
459-
}
460-
461448
// Broadcast to clients via throttler
462449
const worked = this.throttledFileChangesEmitter.work(events);
463450

@@ -528,11 +515,18 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS
528515
const filteredEvents: IFileChange[] = [];
529516
let rootDeleted = false;
530517

518+
const filter = this.isCorrelated(watcher.request) ? watcher.request.filter : undefined; // TODO@bpasero filtering for now is only enabled when correlating because watchers are otherwise potentially reused
531519
for (const event of events) {
532-
rootDeleted = event.type === FileChangeType.DELETED && isEqual(event.resource.fsPath, watcher.request.path, !isLinux);
533520

534-
if (rootDeleted && !this.isCorrelated(watcher.request)) {
521+
// Emit to instance subscriptions if any before filtering
522+
if (watcher.subscriptionsCount > 0) {
523+
watcher.notifyFileChange(event.resource.fsPath, event);
524+
}
535525

526+
// Filtering
527+
rootDeleted = event.type === FileChangeType.DELETED && isEqual(event.resource.fsPath, watcher.request.path, !isLinux);
528+
if (
529+
isFiltered(event, filter) ||
536530
// Explicitly exclude changes to root if we have any
537531
// to avoid VS Code closing all opened editors which
538532
// can happen e.g. in case of network connectivity
@@ -543,10 +537,20 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS
543537
// really do not want to skip over file events any
544538
// more, so we only ignore this event for non-correlated
545539
// watch requests.
540+
(rootDeleted && !this.isCorrelated(watcher.request))
541+
) {
542+
if (this.verboseLogging) {
543+
this.trace(` >> ignored (filtered) ${event.resource.fsPath}`);
544+
}
546545

547546
continue;
548547
}
549548

549+
// Logging
550+
if (this.verboseLogging) {
551+
this.traceEvent(event, watcher.request);
552+
}
553+
550554
filteredEvents.push(event);
551555
}
552556

@@ -831,7 +835,7 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS
831835
this._onDidLogMessage.fire({ type: 'warn', message: this.toMessage(message, watcher) });
832836
}
833837

834-
private error(message: string, watcher: ParcelWatcherInstance | undefined) {
838+
private error(message: string, watcher?: ParcelWatcherInstance) {
835839
this._onDidLogMessage.fire({ type: 'error', message: this.toMessage(message, watcher) });
836840
}
837841

src/vs/platform/files/node/watcher/watcherStats.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { IUniversalWatchRequest } from 'vs/platform/files/common/watcher';
6+
import { IUniversalWatchRequest, requestFilterToString } from 'vs/platform/files/common/watcher';
77
import { INodeJSWatcherInstance, NodeJSWatcher } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcher';
88
import { ParcelWatcher, ParcelWatcherInstance } from 'vs/platform/files/node/watcher/parcel/parcelWatcher';
99

@@ -161,7 +161,7 @@ function fillRequestStats(lines: string[], request: IUniversalWatchRequest, watc
161161
}
162162

163163
function requestDetailsToString(request: IUniversalWatchRequest): string {
164-
return `excludes: ${request.excludes.length > 0 ? request.excludes : '<none>'}, includes: ${request.includes && request.includes.length > 0 ? JSON.stringify(request.includes) : '<all>'}, correlationId: ${typeof request.correlationId === 'number' ? request.correlationId : '<none>'}`;
164+
return `excludes: ${request.excludes.length > 0 ? request.excludes : '<none>'}, includes: ${request.includes && request.includes.length > 0 ? JSON.stringify(request.includes) : '<all>'}, filter: ${requestFilterToString(request.filter)}, correlationId: ${typeof request.correlationId === 'number' ? request.correlationId : '<none>'}`;
165165
}
166166

167167
function fillRecursiveWatcherStats(lines: string[], recursiveWatcher: ParcelWatcher): void {

src/vs/platform/files/test/common/watcher.test.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import { isLinux, isWindows } from 'vs/base/common/platform';
1010
import { isEqual } from 'vs/base/common/resources';
1111
import { URI } from 'vs/base/common/uri';
1212
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
13-
import { FileChangesEvent, FileChangeType, IFileChange } from 'vs/platform/files/common/files';
14-
import { coalesceEvents, reviveFileChanges, parseWatcherPatterns } from 'vs/platform/files/common/watcher';
13+
import { FileChangeFilter, FileChangesEvent, FileChangeType, IFileChange } from 'vs/platform/files/common/files';
14+
import { coalesceEvents, reviveFileChanges, parseWatcherPatterns, isFiltered } from 'vs/platform/files/common/watcher';
1515

1616
class TestFileWatcher extends Disposable {
1717
private readonly _onDidFilesChange: Emitter<{ raw: IFileChange[]; event: FileChangesEvent }>;
@@ -325,5 +325,34 @@ suite('Watcher Events Normalizer', () => {
325325
watch.report(raw);
326326
});
327327

328+
test('event type filter', () => {
329+
const resource = URI.file('/users/data/src/related');
330+
331+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, undefined), false);
332+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, undefined), false);
333+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, undefined), false);
334+
335+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.UPDATED), true);
336+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.UPDATED | FileChangeFilter.DELETED), true);
337+
338+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.ADDED), false);
339+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.ADDED | FileChangeFilter.UPDATED), false);
340+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.ADDED | FileChangeFilter.UPDATED | FileChangeFilter.DELETED), false);
341+
342+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.UPDATED), true);
343+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.UPDATED | FileChangeFilter.ADDED), true);
344+
345+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.DELETED), false);
346+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);
347+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.ADDED | FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);
348+
349+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.ADDED), true);
350+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.DELETED | FileChangeFilter.ADDED), true);
351+
352+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.UPDATED), false);
353+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);
354+
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.ADDED | FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);
355+
});
356+
328357
ensureNoDisposablesAreLeakedInTestSuite();
329358
});

0 commit comments

Comments
 (0)