watcher.ts ×24

Frontier kind: Code frontier

unlabeled · c_8592e77daa43

42 tests · 8981 LOC · 36 files · introduces 0 tests · 249 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
24 ranges249 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1154 ranges8981 lines · 36 files · Browse complete extent
All tests (intent)
42 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 249 introduced LOC across 24 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/files/common/watcher.ts 249 introduced LOC · 24 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- watcher.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { Event } from '../../../base/common/event.js';
7 > import { GLOBSTAR, IRelativePattern, parse, ParsedPattern } from '../../../base/common/glob.js';
8 > import { Disposable, DisposableStore, IDisposable, MutableDisposable } from '../../../base/common/lifecycle.js';
9 > import { isAbsolute } from '../../../base/common/path.js';
10 > import { isLinux } from '../../../base/common/platform.js';
11 > import { URI } from '../../../base/common/uri.js';
12 > import { FileChangeFilter, FileChangeType, IFileChange, isParent } from './files.js';
13 >
14 > interface IWatchRequest {
15 >
16 > /**
17 > * The path to watch.
18 > */
19 > readonly path: string;
20 >
21 > /**
22 > * Whether to watch recursively or not.
23 > */
24 > readonly recursive: boolean;
25 >
26 > /**
27 > * A set of glob patterns or paths to exclude from watching.
28 > */
29 > readonly excludes: string[];
30 >
31 > /**
32 > * An optional set of glob patterns or paths to include for
33 > * watching. If not provided, all paths are considered for
34 > * events.
35 > */
36 > readonly includes?: Array<string | IRelativePattern>;
37 >
38 > /**
39 > * If provided, file change events from the watcher that
40 > * are a result of this watch request will carry the same
41 > * id.
42 > */
43 > 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;
53 > }
54 >
55 > export interface IWatchRequestWithCorrelation extends IWatchRequest {
56 > readonly correlationId: number;
57 > }
58 >
59 > export function isWatchRequestWithCorrelation(request: IWatchRequest): request is IWatchRequestWithCorrelation {
60 return typeof request.correlationId === 'number';
61 }
62 > watcher.ts
63 > export interface INonRecursiveWatchRequest extends IWatchRequest {
64 >
65 > /**
66 > * The watcher will be non-recursive.
67 > */
68 > readonly recursive: false;
69 > }
70 >
71 > export interface IRecursiveWatchRequest extends IWatchRequest {
72 >
73 > /**
74 > * The watcher will be recursive.
75 > */
76 > readonly recursive: true;
77 >
78 > /**
79 > * @deprecated this only exists for WSL1 support and should never
80 > * be used in any other case.
81 > */
82 > pollingInterval?: number;
83 > }
84 >
85 > export function isRecursiveWatchRequest(request: IWatchRequest): request is IRecursiveWatchRequest {
86 return request.recursive === true;
87 }
88 > watcher.ts
89 > export type IUniversalWatchRequest = IRecursiveWatchRequest | INonRecursiveWatchRequest;
90 >
91 > export interface IWatcherErrorEvent {
92 > readonly error: string;
93 > readonly request?: IUniversalWatchRequest;
94 > }
95 >
96 > export interface IWatcher {
97 >
98 > /**
99 > * A normalized file change event from the raw events
100 > * the watcher emits.
101 > */
102 > readonly onDidChangeFile: Event<IFileChange[]>;
103 >
104 > /**
105 > * An event to indicate a message that should get logged.
106 > */
107 > readonly onDidLogMessage: Event<ILogMessage>;
108 >
109 > /**
110 > * An event to indicate an error occurred from the watcher
111 > * that is unrecoverable. Listeners should restart the
112 > * watcher if possible.
113 > */
114 > readonly onDidError: Event<IWatcherErrorEvent>;
115 >
116 > /**
117 > * Configures the watcher to watch according to the
118 > * requests. Any existing watched path that is not
119 > * in the array, will be removed from watching and
120 > * any new path will be added to watching.
121 > */
122 > watch(requests: IWatchRequest[]): Promise<void>;
123 >
124 > /**
125 > * Enable verbose logging in the watcher.
126 > */
127 > setVerboseLogging(enabled: boolean): Promise<void>;
128 >
129 > /**
130 > * Stop all watchers.
131 > */
132 > stop(): Promise<void>;
133 > }
134 >
135 > export interface IRecursiveWatcher extends IWatcher {
136 > watch(requests: IRecursiveWatchRequest[]): Promise<void>;
137 > }
138 >
139 > export interface IRecursiveWatcherWithSubscribe extends IRecursiveWatcher {
140 >
141 > /**
142 > * Subscribe to file events for the given path. The callback is called
143 > * whenever a file event occurs for the path. If the watcher failed,
144 > * the error parameter is set to `true`.
145 > *
146 > * @returns an `IDisposable` to stop listening to events or `undefined`
147 > * if no events can be watched for the path given the current set of
148 > * recursive watch requests.
149 > */
150 > subscribe(path: string, callback: (error: true | null, change?: IFileChange) => void): IDisposable | undefined;
151 > }
152 >
153 > export interface IRecursiveWatcherOptions {
154 >
155 > /**
156 > * If `true`, will enable polling for all watchers, otherwise
157 > * will enable it for paths included in the string array.
158 > *
159 > * @deprecated this only exists for WSL1 support and should never
160 > * be used in any other case.
161 > */
162 > readonly usePolling: boolean | string[];
163 >
164 > /**
165 > * If polling is enabled (via `usePolling`), defines the duration
166 > * in which the watcher will poll for changes.
167 > *
168 > * @deprecated this only exists for WSL1 support and should never
169 > * be used in any other case.
170 > */
171 > readonly pollingInterval?: number;
172 > }
173 >
174 > export interface INonRecursiveWatcher extends IWatcher {
175 > watch(requests: INonRecursiveWatchRequest[]): Promise<void>;
176 > }
177 >
178 > export interface IUniversalWatcher extends IWatcher {
179 > watch(requests: IUniversalWatchRequest[]): Promise<void>;
180 > }
181 >
182 > export abstract class AbstractWatcherClient extends Disposable {
183 >
184 > private static readonly MAX_RESTARTS = 5;
185 >
186 > private watcher: IWatcher | undefined;
187 > private readonly watcherDisposables = this._register(new MutableDisposable());
188 >
189 > private requests: IWatchRequest[] | undefined = undefined;
190 >
191 > private restartCounter = 0;
192 >
193 > constructor(
194 private readonly onFileChanges: (changes: IFileChange[]) => void,
195 private readonly onLogMessage: (msg: ILogMessage) => void,
202 super();
203 }
204 > watcher.ts
205 > protected abstract createWatcher(disposables: DisposableStore): IWatcher;
206 >
207 > protected init(): void {
208
209 // Associate disposables to the watcher
220 disposables.add(this.watcher.onDidError(e => this.onError(e.error, e.request)));
221 }
222 > watcher.ts
223 > protected onError(error: string, failedRequest?: IUniversalWatchRequest): void {
224
225 // Restart on error (up to N times, if possible)
238 }
239 }
240 > watcher.ts
241 > private canRestart(error: string, failedRequest?: IUniversalWatchRequest): boolean {
242 if (!this.options.restartOnError) {
243 return false; // disabled by options
264 return true;
265 }
266 > watcher.ts
267 > private restart(requests: IUniversalWatchRequest[]): void {
268 this.restartCounter++;
269
271 this.watch(requests);
272 }
273 > watcher.ts
274 > async watch(requests: IUniversalWatchRequest[]): Promise<void> {
275 this.requests = requests;
276
277 await this.watcher?.watch(requests);
278 }
279 > watcher.ts
280 > async setVerboseLogging(verboseLogging: boolean): Promise<void> {
281 this.verboseLogging = verboseLogging;
282
283 await this.watcher?.setVerboseLogging(verboseLogging);
284 }
285 > watcher.ts
286 > private error(message: string) {
287 this.onLogMessage({ type: 'error', message: `[File Watcher (${this.options.type})] ${message}` });
288 }
289 > watcher.ts
290 > protected trace(message: string) {
291 this.onLogMessage({ type: 'trace', message: `[File Watcher (${this.options.type})] ${message}` });
292 }
293 > watcher.ts
294 > override dispose(): void {
295
296 // Render the watcher invalid from here
299 return super.dispose();
300 }
301 > } watcher.ts
302 >
303 > export abstract class AbstractNonRecursiveWatcherClient extends AbstractWatcherClient {
304 >
305 > constructor(
306 onFileChanges: (changes: IFileChange[]) => void,
307 onLogMessage: (msg: ILogMessage) => void,
310 super(onFileChanges, onLogMessage, verboseLogging, { type: 'node.js', restartOnError: false });
311 }
312 > watcher.ts
313 > protected abstract override createWatcher(disposables: DisposableStore): INonRecursiveWatcher;
314 > }
315 >
316 > export abstract class AbstractUniversalWatcherClient extends AbstractWatcherClient {
317 >
318 > constructor(
319 onFileChanges: (changes: IFileChange[]) => void,
320 onLogMessage: (msg: ILogMessage) => void,
323 super(onFileChanges, onLogMessage, verboseLogging, { type: 'universal', restartOnError: true });
324 }
325 > watcher.ts
326 > protected abstract override createWatcher(disposables: DisposableStore): IUniversalWatcher;
327 > }
328 >
329 > export interface ILogMessage {
330 > readonly type: 'trace' | 'warn' | 'error' | 'info' | 'debug';
331 > readonly message: string;
332 > }
333 >
334 > export function reviveFileChanges(changes: IFileChange[]): IFileChange[] {
335 return changes.map(change => ({
336 type: change.type,
339 }));
340 }
341 > watcher.ts
342 > export function coalesceEvents(changes: IFileChange[]): IFileChange[] {
343
344 // Build deltas
350 return coalescer.coalesce();
351 }
352 > watcher.ts
353 > export function normalizeWatcherPattern(path: string, pattern: string | IRelativePattern): string | IRelativePattern {
354
355 // Patterns are always matched on the full absolute path
365 return pattern;
366 }
367 > watcher.ts
368 > export function parseWatcherPatterns(path: string, patterns: Array<string | IRelativePattern>, ignoreCase: boolean): ParsedPattern[] {
369 const parsedPatterns: ParsedPattern[] = [];
370
375 return parsedPatterns;
376 }
377 > watcher.ts
378 class EventCoalescer {
379
380 private readonly coalesced = new Set<IFileChange>();
381 private readonly mapPathToChange = new Map<string, IFileChange>();
382 > watcher.ts
383 > private toKey(event: IFileChange): string {
384 if (isLinux) {
385 return event.resource.fsPath;
388 return event.resource.fsPath.toLowerCase(); // normalise to file system case sensitivity
389 }
390 > watcher.ts
391 > processEvent(event: IFileChange): void {
392 const existingEvent = this.mapPathToChange.get(this.toKey(event));
393
435 }
436 }
437 > watcher.ts
438 > coalesce(): IFileChange[] {
439 const addOrChangeEvents: IFileChange[] = [];
440 const deletedPaths: string[] = [];
468 }).concat(addOrChangeEvents);
469 }
470 > } watcher.ts
471 >
472 > export function isFiltered(event: IFileChange, filter: FileChangeFilter | undefined): boolean {
473 if (typeof filter === 'number') {
474 switch (event.type) {
484 return false;
485 }
486 > watcher.ts
487 > export function requestFilterToString(filter: FileChangeFilter | undefined): string {
488 if (typeof filter === 'number') {
489 const filters = [];