@theia/filesystem 1.77.0-next.6 → 1.77.0-next.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/lib/browser/download/file-download-service.d.ts.map +1 -1
  2. package/lib/browser/download/file-download-service.js +0 -1
  3. package/lib/browser/download/file-download-service.js.map +1 -1
  4. package/lib/browser/file-dialog/file-dialog.js +1 -1
  5. package/lib/browser/file-dialog/file-dialog.js.map +1 -1
  6. package/lib/browser/file-resource.spec.js +2 -1
  7. package/lib/browser/file-resource.spec.js.map +1 -1
  8. package/lib/browser/file-service-watcher.spec.js +2 -1
  9. package/lib/browser/file-service-watcher.spec.js.map +1 -1
  10. package/lib/browser/file-service.d.ts +1 -1
  11. package/lib/browser/file-service.js +1 -1
  12. package/lib/browser/file-service.js.map +1 -1
  13. package/lib/browser/file-tree/file-tree-decorator-adapter.js +1 -1
  14. package/lib/browser/file-tree/file-tree-decorator-adapter.js.map +1 -1
  15. package/lib/browser/file-tree/file-tree-decorator-adapter.spec.js +2 -1
  16. package/lib/browser/file-tree/file-tree-decorator-adapter.spec.js.map +1 -1
  17. package/lib/browser/file-tree/file-tree-model.js +1 -1
  18. package/lib/browser/file-tree/file-tree-model.js.map +1 -1
  19. package/lib/browser/file-tree/file-tree-widget.js +1 -1
  20. package/lib/browser/file-tree/file-tree-widget.js.map +1 -1
  21. package/lib/browser/filesystem-frontend-contribution.js +1 -1
  22. package/lib/browser/filesystem-frontend-contribution.js.map +1 -1
  23. package/lib/browser/location/location-renderer.js +1 -1
  24. package/lib/browser/location/location-renderer.js.map +1 -1
  25. package/lib/browser/location/location-renderer.spec.js +2 -1
  26. package/lib/browser/location/location-renderer.spec.js.map +1 -1
  27. package/lib/browser/upload/file-upload-service-impl.js +1 -1
  28. package/lib/browser/upload/file-upload-service-impl.js.map +1 -1
  29. package/lib/browser-only/download/file-download-service.js +1 -1
  30. package/lib/browser-only/download/file-download-service.js.map +1 -1
  31. package/lib/browser-only/file-search.js +2 -1
  32. package/lib/browser-only/file-search.js.map +1 -1
  33. package/lib/browser-only/upload/file-upload-service-impl.js +1 -1
  34. package/lib/browser-only/upload/file-upload-service-impl.js.map +1 -1
  35. package/lib/common/files.js +2 -1
  36. package/lib/common/files.js.map +1 -1
  37. package/lib/common/files.spec.js +2 -1
  38. package/lib/common/files.spec.js.map +1 -1
  39. package/lib/common/filesystem-watcher-protocol.d.ts +4 -0
  40. package/lib/common/filesystem-watcher-protocol.d.ts.map +1 -1
  41. package/lib/common/remote-file-system-provider.d.ts +4 -4
  42. package/lib/common/remote-file-system-provider.d.ts.map +1 -1
  43. package/lib/common/remote-file-system-provider.js +1 -1
  44. package/lib/common/remote-file-system-provider.js.map +1 -1
  45. package/lib/node/disk-file-system-provider.d.ts.map +1 -1
  46. package/lib/node/disk-file-system-provider.js +8 -5
  47. package/lib/node/disk-file-system-provider.js.map +1 -1
  48. package/lib/node/disk-file-system-provider.spec.js +28 -2
  49. package/lib/node/disk-file-system-provider.spec.js.map +1 -1
  50. package/lib/node/download/directory-archiver.js +2 -2
  51. package/lib/node/download/directory-archiver.js.map +1 -1
  52. package/lib/node/download/directory-archiver.spec.js +5 -4
  53. package/lib/node/download/directory-archiver.spec.js.map +1 -1
  54. package/lib/node/download/file-download-handler.d.ts.map +1 -1
  55. package/lib/node/download/file-download-handler.js +4 -5
  56. package/lib/node/download/file-download-handler.js.map +1 -1
  57. package/lib/node/file-change-collection.spec.js +2 -1
  58. package/lib/node/file-change-collection.spec.js.map +1 -1
  59. package/lib/node/filesystem-backend-module.d.ts +2 -2
  60. package/lib/node/filesystem-backend-module.d.ts.map +1 -1
  61. package/lib/node/filesystem-backend-module.js +7 -5
  62. package/lib/node/filesystem-backend-module.js.map +1 -1
  63. package/lib/node/filesystem-watcher-service-impl.d.ts +30 -0
  64. package/lib/node/filesystem-watcher-service-impl.d.ts.map +1 -0
  65. package/lib/node/filesystem-watcher-service-impl.js +92 -0
  66. package/lib/node/filesystem-watcher-service-impl.js.map +1 -0
  67. package/lib/node/filesystem-watcher-service-impl.spec.d.ts +2 -0
  68. package/lib/node/filesystem-watcher-service-impl.spec.d.ts.map +1 -0
  69. package/lib/node/filesystem-watcher-service-impl.spec.js +202 -0
  70. package/lib/node/filesystem-watcher-service-impl.spec.js.map +1 -0
  71. package/lib/node/filesystem-watcher.d.ts +93 -0
  72. package/lib/node/filesystem-watcher.d.ts.map +1 -0
  73. package/lib/node/filesystem-watcher.js +134 -0
  74. package/lib/node/filesystem-watcher.js.map +1 -0
  75. package/lib/node/nodejs-watcher/node-directory-watcher.d.ts +175 -0
  76. package/lib/node/nodejs-watcher/node-directory-watcher.d.ts.map +1 -0
  77. package/lib/node/nodejs-watcher/node-directory-watcher.js +580 -0
  78. package/lib/node/nodejs-watcher/node-directory-watcher.js.map +1 -0
  79. package/lib/node/nodejs-watcher/node-directory-watcher.spec.d.ts +2 -0
  80. package/lib/node/nodejs-watcher/node-directory-watcher.spec.d.ts.map +1 -0
  81. package/lib/node/nodejs-watcher/node-directory-watcher.spec.js +1284 -0
  82. package/lib/node/nodejs-watcher/node-directory-watcher.spec.js.map +1 -0
  83. package/lib/node/nodejs-watcher/watch-request-router.d.ts +63 -0
  84. package/lib/node/nodejs-watcher/watch-request-router.d.ts.map +1 -0
  85. package/lib/node/nodejs-watcher/watch-request-router.js +183 -0
  86. package/lib/node/nodejs-watcher/watch-request-router.js.map +1 -0
  87. package/lib/node/nodejs-watcher/watcher-host.d.ts +50 -0
  88. package/lib/node/nodejs-watcher/watcher-host.d.ts.map +1 -0
  89. package/lib/node/nodejs-watcher/watcher-host.js +99 -0
  90. package/lib/node/nodejs-watcher/watcher-host.js.map +1 -0
  91. package/lib/node/parcel-watcher/index.js +4 -3
  92. package/lib/node/parcel-watcher/index.js.map +1 -1
  93. package/lib/node/parcel-watcher/parcel-filesystem-service.d.ts +14 -194
  94. package/lib/node/parcel-watcher/parcel-filesystem-service.d.ts.map +1 -1
  95. package/lib/node/parcel-watcher/parcel-filesystem-service.js +7 -424
  96. package/lib/node/parcel-watcher/parcel-filesystem-service.js.map +1 -1
  97. package/lib/node/parcel-watcher/parcel-filesystem-watcher.spec.js +8 -7
  98. package/lib/node/parcel-watcher/parcel-filesystem-watcher.spec.js.map +1 -1
  99. package/lib/node/parcel-watcher/parcel-watcher-exclude.spec.js +8 -7
  100. package/lib/node/parcel-watcher/parcel-watcher-exclude.spec.js.map +1 -1
  101. package/lib/node/parcel-watcher/parcel-watcher-retry.spec.js +7 -6
  102. package/lib/node/parcel-watcher/parcel-watcher-retry.spec.js.map +1 -1
  103. package/lib/node/parcel-watcher/parcel-watcher.d.ts +165 -0
  104. package/lib/node/parcel-watcher/parcel-watcher.d.ts.map +1 -0
  105. package/lib/node/parcel-watcher/parcel-watcher.js +370 -0
  106. package/lib/node/parcel-watcher/parcel-watcher.js.map +1 -0
  107. package/lib/node/test/watcher-test-helper.d.ts +21 -0
  108. package/lib/node/test/watcher-test-helper.d.ts.map +1 -0
  109. package/lib/node/test/watcher-test-helper.js +56 -0
  110. package/lib/node/test/watcher-test-helper.js.map +1 -0
  111. package/lib/node/upload/node-file-upload-service.spec.js +4 -3
  112. package/lib/node/upload/node-file-upload-service.spec.js.map +1 -1
  113. package/package.json +7 -7
  114. package/src/browser/download/file-download-service.ts +0 -1
  115. package/src/browser/file-service.ts +3 -3
  116. package/src/common/filesystem-watcher-protocol.ts +4 -0
  117. package/src/node/disk-file-system-provider.spec.ts +29 -0
  118. package/src/node/disk-file-system-provider.ts +5 -2
  119. package/src/node/download/file-download-handler.ts +0 -1
  120. package/src/node/filesystem-backend-module.ts +5 -4
  121. package/src/node/filesystem-watcher-service-impl.spec.ts +272 -0
  122. package/src/node/filesystem-watcher-service-impl.ts +109 -0
  123. package/src/node/filesystem-watcher.ts +197 -0
  124. package/src/node/nodejs-watcher/node-directory-watcher.spec.ts +1594 -0
  125. package/src/node/nodejs-watcher/node-directory-watcher.ts +664 -0
  126. package/src/node/nodejs-watcher/watch-request-router.ts +224 -0
  127. package/src/node/nodejs-watcher/watcher-host.ts +126 -0
  128. package/src/node/parcel-watcher/index.ts +2 -2
  129. package/src/node/parcel-watcher/parcel-filesystem-service.ts +14 -500
  130. package/src/node/parcel-watcher/parcel-filesystem-watcher.spec.ts +4 -4
  131. package/src/node/parcel-watcher/parcel-watcher-exclude.spec.ts +4 -4
  132. package/src/node/parcel-watcher/parcel-watcher-retry.spec.ts +3 -3
  133. package/src/node/parcel-watcher/parcel-watcher.ts +432 -0
  134. package/src/node/test/watcher-test-helper.ts +58 -0
@@ -0,0 +1,224 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 Ehab Younes and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import * as path from 'path';
18
+ import { Minimatch } from 'minimatch';
19
+ import { FileUri } from '@theia/core/lib/common/file-uri';
20
+ import { FileChangeType, FileSystemWatcherServiceClient } from '../../common/filesystem-watcher-protocol';
21
+ import { FileChangeCollection } from '../file-change-collection';
22
+ import { WatchRequest } from '../filesystem-watcher';
23
+ import { WatcherHost } from './watcher-host';
24
+
25
+ /** A {@link WatchRequest} resolved against the file system. */
26
+ export interface DirectoryWatchRequest extends WatchRequest {
27
+ /** Where `path` points: the watched directory (every child) or one file in it. Reports use `path`. */
28
+ readonly realPath: string;
29
+ /** Whether the client takes `path` for existing: read when it asked, then kept to what it was last told. */
30
+ readonly exists: boolean;
31
+ }
32
+
33
+ /** A resolved change to a direct child of the watched directory. */
34
+ export interface ResolvedChange {
35
+ readonly fileName: string;
36
+ readonly type: FileChangeType;
37
+ /** Set on a deletion whose name still exists in another case. */
38
+ readonly pathRemains?: boolean;
39
+ }
40
+
41
+ /** A change as one client will be told about it. */
42
+ interface ReportedChange {
43
+ readonly clientId: number;
44
+ readonly filePath: string;
45
+ readonly type: FileChangeType;
46
+ }
47
+
48
+ /**
49
+ * Holds the requests one watcher serves and turns resolved changes into notifications. It touches none of the
50
+ * event state machine, and asks for the watched directory rather than storing it.
51
+ */
52
+ export class WatchRequestRouter {
53
+
54
+ private readonly requests = new Map<number, DirectoryWatchRequest>();
55
+ /** Requests moved in from another watcher, which hear nothing until {@link admitAdopted}. */
56
+ private readonly adopted = new Map<number, DirectoryWatchRequest>();
57
+ /** Compiled excludes, shared across requests, which mostly carry the same `files.watcherExclude`. */
58
+ private readonly matchers = new Map<string, Minimatch>();
59
+
60
+ constructor(
61
+ protected readonly client: FileSystemWatcherServiceClient,
62
+ protected readonly host: WatcherHost,
63
+ protected readonly watchedDirectory: () => string
64
+ ) { }
65
+
66
+ get size(): number {
67
+ return this.requests.size + this.adopted.size;
68
+ }
69
+
70
+ add(watcherId: number, request: DirectoryWatchRequest): void {
71
+ this.requests.set(watcherId, request);
72
+ }
73
+
74
+ /** Holds requests moved in from another watcher until {@link admitAdopted}. */
75
+ adopt(requests: Map<number, DirectoryWatchRequest>): void {
76
+ requests.forEach((request, watcherId) => this.adopted.set(watcherId, request));
77
+ }
78
+
79
+ /** @returns `true` if a request was registered under `watcherId`. */
80
+ remove(watcherId: number): boolean {
81
+ return this.requests.delete(watcherId) || this.adopted.delete(watcherId);
82
+ }
83
+
84
+ /** Drains the requests, so that they can be moved to the watcher serving their directory. */
85
+ takeAll(): Map<number, DirectoryWatchRequest> {
86
+ const taken = new Map([...this.requests, ...this.adopted]);
87
+ this.requests.clear();
88
+ this.adopted.clear();
89
+ return taken;
90
+ }
91
+
92
+ /** Lets the adopted requests hear changes. */
93
+ admitAdopted(): void {
94
+ this.adopted.forEach((request, watcherId) => this.requests.set(watcherId, request));
95
+ this.adopted.clear();
96
+ }
97
+
98
+ /** @returns a test of whether a request's path is the watched directory or one of `children`. */
99
+ knows(children: ReadonlySet<string>): (request: DirectoryWatchRequest) => boolean {
100
+ let childKeys: Set<string> | undefined;
101
+ return request => {
102
+ if (this.watchesDirectory(request)) {
103
+ return true;
104
+ }
105
+ const fileName = this.childName(request);
106
+ if (fileName === undefined || children.has(fileName)) {
107
+ return fileName !== undefined;
108
+ }
109
+ // Keyed at most once per call, and only when a name ignoring case can differ from the stored one.
110
+ childKeys ??= this.host.caseInsensitiveFileNames ? new Set(Array.from(children, child => this.host.pathKey(child))) : new Set();
111
+ return childKeys.has(this.host.pathKey(fileName));
112
+ };
113
+ }
114
+
115
+ /** @returns `true` if every request is for `fsPath` itself rather than for something inside it. */
116
+ allFor(fsPath: string): boolean {
117
+ return [...this.requests.values(), ...this.adopted.values()].every(request => this.host.samePath(request.realPath, fsPath));
118
+ }
119
+
120
+ /** @returns `true` if the request is for the watched directory, rather than for one file in it. */
121
+ watchesDirectory(request: DirectoryWatchRequest): boolean {
122
+ // Both sides are resolved separately, so compare them the way the host resolves names.
123
+ return this.host.samePath(request.realPath, this.watchedDirectory());
124
+ }
125
+
126
+ /** Points every request made for `target` at where it resolved to, a file or a directory. */
127
+ resolveRequests(target: string, realPath: string): void {
128
+ for (const [watcherId, request] of this.requests) {
129
+ if (request.path === target) {
130
+ this.requests.set(watcherId, { ...request, realPath });
131
+ }
132
+ }
133
+ }
134
+
135
+ /** Reports changes to direct children, mapped and filtered per request. */
136
+ report(changes: readonly ResolvedChange[]): void {
137
+ // Keyed once per batch, so a file request looks up its own changes rather than comparing against each one.
138
+ const changesByKey = new Map<string, ResolvedChange[]>();
139
+ for (const change of changes) {
140
+ const key = this.host.pathKey(change.fileName);
141
+ let changesToChild = changesByKey.get(key);
142
+ if (!changesToChild) {
143
+ changesByKey.set(key, changesToChild = []);
144
+ }
145
+ changesToChild.push(change);
146
+ }
147
+ const reported: ReportedChange[] = [];
148
+ for (const [watcherId, request] of this.requests) {
149
+ if (this.watchesDirectory(request)) {
150
+ for (const { fileName, type } of changes) {
151
+ const childPath = path.resolve(request.path, fileName);
152
+ if (!this.isIgnored(request, childPath)) {
153
+ reported.push({ clientId: request.clientId, filePath: childPath, type });
154
+ }
155
+ }
156
+ continue;
157
+ }
158
+ const ownName = this.childName(request);
159
+ // Excludes filter children, never the path a client explicitly asked to watch.
160
+ for (const { type, pathRemains } of ownName === undefined ? [] : changesByKey.get(this.host.pathKey(ownName)) ?? []) {
161
+ this.tell(reported, watcherId, pathRemains ? FileChangeType.UPDATED : type);
162
+ }
163
+ }
164
+ this.emit(reported);
165
+ }
166
+
167
+ /** Tells each request where `exists` differs from what it takes its own path for, so it hears of a loss or a recovery once. */
168
+ reconcile(exists: (request: DirectoryWatchRequest) => boolean): void {
169
+ const reported: ReportedChange[] = [];
170
+ for (const [watcherId, request] of this.requests) {
171
+ this.tell(reported, watcherId, exists(request) ? FileChangeType.ADDED : FileChangeType.DELETED);
172
+ }
173
+ this.emit(reported);
174
+ }
175
+
176
+ /** Adds a change to a request's own path, unless the client already takes the path for what it reports. */
177
+ private tell(reported: ReportedChange[], watcherId: number, type: FileChangeType): void {
178
+ const request = this.requests.get(watcherId)!;
179
+ if (type !== FileChangeType.UPDATED) {
180
+ const exists = type === FileChangeType.ADDED;
181
+ if (request.exists === exists) {
182
+ return;
183
+ }
184
+ this.requests.set(watcherId, { ...request, exists });
185
+ }
186
+ reported.push({ clientId: request.clientId, filePath: request.path, type });
187
+ }
188
+
189
+ /** Notifies each client once per watched path, so overlapping requests do not report twice. */
190
+ private emit(reported: readonly ReportedChange[]): void {
191
+ if (reported.length === 0) {
192
+ return;
193
+ }
194
+ const perClient = new Map<number, FileChangeCollection>();
195
+ for (const { clientId, filePath, type } of reported) {
196
+ let collection = perClient.get(clientId);
197
+ if (!collection) {
198
+ perClient.set(clientId, collection = new FileChangeCollection());
199
+ }
200
+ collection.push({ uri: FileUri.create(filePath).toString(), type });
201
+ }
202
+ for (const [clientId, collection] of perClient) {
203
+ this.client.onDidFilesChanged({ clients: [clientId], changes: collection.values() });
204
+ }
205
+ }
206
+
207
+ /** The name of the child a file request is for, or `undefined` if its path is not in the watched directory. */
208
+ protected childName(request: DirectoryWatchRequest): string | undefined {
209
+ const fileName = this.host.normalizeFileName(path.basename(request.realPath));
210
+ return this.host.samePath(path.resolve(this.watchedDirectory(), fileName), request.realPath) ? fileName : undefined;
211
+ }
212
+
213
+ protected isIgnored(request: DirectoryWatchRequest, changed: string): boolean {
214
+ return request.ignored.some(pattern => this.matcher(pattern).match(changed));
215
+ }
216
+
217
+ private matcher(pattern: string): Minimatch {
218
+ let matcher = this.matchers.get(pattern);
219
+ if (!matcher) {
220
+ this.matchers.set(pattern, matcher = new Minimatch(pattern, { dot: true }));
221
+ }
222
+ return matcher;
223
+ }
224
+ }
@@ -0,0 +1,126 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 Ehab Younes and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import * as path from 'path';
18
+ import * as fs from 'fs';
19
+ import { isOSX, isWindows } from '@theia/core';
20
+ import { WatcherLogger } from '../filesystem-watcher';
21
+
22
+ export type WatchEventListener = (eventType: string, fileName: string | null) => void;
23
+
24
+ /** Enough of a directory to tell a replacement from the original. */
25
+ export interface DirectoryIdentity {
26
+ readonly dev: number;
27
+ readonly ino: number;
28
+ readonly birthtimeMs: number;
29
+ }
30
+
31
+ /** Where a requested path is watched, and what the path itself resolves to. */
32
+ export interface WatchTarget {
33
+ /** Directory to open a handle on. */
34
+ readonly directory: string;
35
+ /** The requested path resolved: `directory` itself, or one file inside it. */
36
+ readonly realPath: string;
37
+ /** Whether `realPath` exists. */
38
+ readonly exists: boolean;
39
+ }
40
+
41
+ /** Every file system and platform rule a watcher needs, in one place a test can substitute. */
42
+ export class WatcherHost {
43
+
44
+ private statFailureReported = false;
45
+
46
+ constructor(protected readonly logger: WatcherLogger) { }
47
+
48
+ /** Windows and macOS resolve names irrespective of case. */
49
+ get caseInsensitiveFileNames(): boolean {
50
+ return isWindows || isOSX;
51
+ }
52
+
53
+ /** macOS crashes on watching a network share (microsoft/vscode#106879). */
54
+ isUnsupportedTarget(fsPath: string): boolean {
55
+ return isOSX && (fsPath === '/Volumes' || fsPath.startsWith('/Volumes/'));
56
+ }
57
+
58
+ /**
59
+ * A file is watched through its parent, which also keeps one that is deleted and recreated observable. A
60
+ * path that does not exist yet is taken for a directory. Real paths are resolved because macOS FSEvents
61
+ * reports them and libuv drops what it cannot match.
62
+ */
63
+ async resolveTarget(fsPath: string): Promise<WatchTarget> {
64
+ const realPath = await fs.promises.realpath(fsPath).catch(() => fsPath);
65
+ const stat = await fs.promises.stat(realPath).catch(() => undefined);
66
+ return { directory: stat && !stat.isDirectory() ? path.dirname(realPath) : realPath, realPath, exists: stat !== undefined };
67
+ }
68
+
69
+ watch(directory: string, listener: WatchEventListener): fs.FSWatcher {
70
+ return fs.watch(directory, { recursive: false }, listener);
71
+ }
72
+
73
+ async readChildren(directory: string): Promise<Set<string>> {
74
+ const children = await fs.promises.readdir(directory).catch(() => []);
75
+ return new Set(children.map(fileName => this.normalizeFileName(fileName)));
76
+ }
77
+
78
+ /** The identity of a directory, or `undefined` if the path is missing or not a directory. */
79
+ async readIdentity(directory: string): Promise<DirectoryIdentity | undefined> {
80
+ const stat = await fs.promises.stat(directory).catch(() => undefined);
81
+ // The inode number alone is not enough: deleting a directory frees it for its replacement.
82
+ return stat?.isDirectory() ? { dev: stat.dev, ino: stat.ino, birthtimeMs: stat.birthtimeMs } : undefined;
83
+ }
84
+
85
+ /**
86
+ * Looks up the stored name of children, for several names at one moment. Where names ignore case, it reads the
87
+ * directory once, as `stat` would hide a `foo.txt` to `Foo.txt` rename.
88
+ */
89
+ childLookup(directory: string): (fileName: string) => Promise<string | undefined> {
90
+ if (!this.caseInsensitiveFileNames) {
91
+ return async fileName => await this.exists(path.resolve(directory, fileName)) ? fileName : undefined;
92
+ }
93
+ let stored: Promise<{ names: Set<string>, byKey: Map<string, string> }> | undefined;
94
+ return async fileName => {
95
+ const { names, byKey } = await (stored ??= this.readChildren(directory)
96
+ .then(children => ({ names: children, byKey: new Map(Array.from(children, child => [this.pathKey(child), child])) })));
97
+ return names.has(fileName) ? fileName : byKey.get(this.pathKey(fileName));
98
+ };
99
+ }
100
+
101
+ exists(fsPath: string): Promise<boolean> {
102
+ return fs.promises.stat(fsPath).then(() => true, error => {
103
+ // Only ENOENT and ENOTDIR mean missing. EACCES or ELOOP can turn into a spurious deletion and creation, so log them.
104
+ if (!['ENOENT', 'ENOTDIR'].includes(error?.code) && !this.statFailureReported) {
105
+ this.statFailureReported = true;
106
+ this.logger.error(`Watcher cannot tell whether "${fsPath}" exists, treating it as missing:`, error);
107
+ }
108
+ return false;
109
+ });
110
+ }
111
+
112
+ /** macOS reports decomposed names, which would not match a composed path a client asked to watch. */
113
+ normalizeFileName(fileName: string): string {
114
+ return isOSX ? fileName.normalize('NFC') : fileName;
115
+ }
116
+
117
+ /** The form in which the host compares paths. A path read from the disk can be decomposed on macOS. */
118
+ pathKey(fsPath: string): string {
119
+ const normalized = this.normalizeFileName(fsPath);
120
+ return this.caseInsensitiveFileNames ? normalized.toLowerCase() : normalized;
121
+ }
122
+
123
+ samePath(expected: string, actual: string): boolean {
124
+ return this.pathKey(expected) === this.pathKey(actual);
125
+ }
126
+ }
@@ -17,7 +17,7 @@
17
17
  import * as yargs from '@theia/core/shared/yargs';
18
18
  import { RpcProxyFactory } from '@theia/core';
19
19
  import { FileSystemWatcherServiceClient } from '../../common/filesystem-watcher-protocol';
20
- import { ParcelFileSystemWatcherService } from './parcel-filesystem-service';
20
+ import { FileSystemWatcherServiceImpl } from '../filesystem-watcher-service-impl';
21
21
  import { IPCEntryPoint } from '@theia/core/lib/node/messaging/ipc-protocol';
22
22
 
23
23
  /* eslint-disable @typescript-eslint/no-explicit-any */
@@ -38,7 +38,7 @@ const options: {
38
38
  .argv as any;
39
39
 
40
40
  export default <IPCEntryPoint>(connection => {
41
- const server = new ParcelFileSystemWatcherService(options);
41
+ const server = new FileSystemWatcherServiceImpl(options);
42
42
  const factory = new RpcProxyFactory<FileSystemWatcherServiceClient>(server);
43
43
  server.setClient(factory.createProxy());
44
44
  factory.listen(connection);