@theia/filesystem 1.77.0-next.7 → 1.77.0-next.77

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 (163) 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-tree-filters-renderer.d.ts +2 -0
  5. package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.d.ts.map +1 -1
  6. package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.js +1 -1
  7. package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.js.map +1 -1
  8. package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.spec.d.ts +2 -0
  9. package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.spec.d.ts.map +1 -0
  10. package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.spec.js +37 -0
  11. package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.spec.js.map +1 -0
  12. package/lib/browser/file-dialog/file-dialog.d.ts +5 -0
  13. package/lib/browser/file-dialog/file-dialog.d.ts.map +1 -1
  14. package/lib/browser/file-dialog/file-dialog.js +6 -2
  15. package/lib/browser/file-dialog/file-dialog.js.map +1 -1
  16. package/lib/browser/file-resource.spec.js +2 -1
  17. package/lib/browser/file-resource.spec.js.map +1 -1
  18. package/lib/browser/file-service-copy.spec.d.ts +2 -0
  19. package/lib/browser/file-service-copy.spec.d.ts.map +1 -0
  20. package/lib/browser/file-service-copy.spec.js +196 -0
  21. package/lib/browser/file-service-copy.spec.js.map +1 -0
  22. package/lib/browser/file-service-watcher.spec.js +2 -1
  23. package/lib/browser/file-service-watcher.spec.js.map +1 -1
  24. package/lib/browser/file-service.d.ts +1 -1
  25. package/lib/browser/file-service.js +3 -3
  26. package/lib/browser/file-service.js.map +1 -1
  27. package/lib/browser/file-tree/file-tree-decorator-adapter.js +1 -1
  28. package/lib/browser/file-tree/file-tree-decorator-adapter.js.map +1 -1
  29. package/lib/browser/file-tree/file-tree-decorator-adapter.spec.js +2 -1
  30. package/lib/browser/file-tree/file-tree-decorator-adapter.spec.js.map +1 -1
  31. package/lib/browser/file-tree/file-tree-model.js +1 -1
  32. package/lib/browser/file-tree/file-tree-model.js.map +1 -1
  33. package/lib/browser/file-tree/file-tree-widget.js +1 -1
  34. package/lib/browser/file-tree/file-tree-widget.js.map +1 -1
  35. package/lib/browser/filesystem-frontend-contribution.js +1 -1
  36. package/lib/browser/filesystem-frontend-contribution.js.map +1 -1
  37. package/lib/browser/location/location-renderer.js +1 -1
  38. package/lib/browser/location/location-renderer.js.map +1 -1
  39. package/lib/browser/location/location-renderer.spec.js +2 -1
  40. package/lib/browser/location/location-renderer.spec.js.map +1 -1
  41. package/lib/browser/upload/file-upload-service-impl.js +1 -1
  42. package/lib/browser/upload/file-upload-service-impl.js.map +1 -1
  43. package/lib/browser-only/download/file-download-service.js +1 -1
  44. package/lib/browser-only/download/file-download-service.js.map +1 -1
  45. package/lib/browser-only/file-search.js +2 -1
  46. package/lib/browser-only/file-search.js.map +1 -1
  47. package/lib/browser-only/upload/file-upload-service-impl.js +1 -1
  48. package/lib/browser-only/upload/file-upload-service-impl.js.map +1 -1
  49. package/lib/common/files.js +2 -1
  50. package/lib/common/files.js.map +1 -1
  51. package/lib/common/files.spec.js +2 -1
  52. package/lib/common/files.spec.js.map +1 -1
  53. package/lib/common/filesystem-watcher-protocol.d.ts +4 -0
  54. package/lib/common/filesystem-watcher-protocol.d.ts.map +1 -1
  55. package/lib/common/remote-file-system-provider.d.ts +4 -4
  56. package/lib/common/remote-file-system-provider.d.ts.map +1 -1
  57. package/lib/common/remote-file-system-provider.js +1 -1
  58. package/lib/common/remote-file-system-provider.js.map +1 -1
  59. package/lib/electron-browser/file-dialog/electron-file-dialog-service.js +1 -1
  60. package/lib/electron-browser/file-dialog/electron-file-dialog-service.js.map +1 -1
  61. package/lib/electron-browser/file-dialog/electron-file-dialog-service.spec.d.ts +2 -0
  62. package/lib/electron-browser/file-dialog/electron-file-dialog-service.spec.d.ts.map +1 -0
  63. package/lib/electron-browser/file-dialog/electron-file-dialog-service.spec.js +48 -0
  64. package/lib/electron-browser/file-dialog/electron-file-dialog-service.spec.js.map +1 -0
  65. package/lib/node/disk-file-system-provider.d.ts.map +1 -1
  66. package/lib/node/disk-file-system-provider.js +8 -5
  67. package/lib/node/disk-file-system-provider.js.map +1 -1
  68. package/lib/node/disk-file-system-provider.spec.js +28 -2
  69. package/lib/node/disk-file-system-provider.spec.js.map +1 -1
  70. package/lib/node/download/directory-archiver.js +2 -2
  71. package/lib/node/download/directory-archiver.js.map +1 -1
  72. package/lib/node/download/directory-archiver.spec.js +5 -4
  73. package/lib/node/download/directory-archiver.spec.js.map +1 -1
  74. package/lib/node/download/file-download-cache.js +1 -1
  75. package/lib/node/download/file-download-cache.js.map +1 -1
  76. package/lib/node/download/file-download-handler.d.ts.map +1 -1
  77. package/lib/node/download/file-download-handler.js +4 -5
  78. package/lib/node/download/file-download-handler.js.map +1 -1
  79. package/lib/node/file-change-collection.spec.js +2 -1
  80. package/lib/node/file-change-collection.spec.js.map +1 -1
  81. package/lib/node/filesystem-backend-module.d.ts +2 -2
  82. package/lib/node/filesystem-backend-module.d.ts.map +1 -1
  83. package/lib/node/filesystem-backend-module.js +7 -5
  84. package/lib/node/filesystem-backend-module.js.map +1 -1
  85. package/lib/node/filesystem-watcher-service-impl.d.ts +30 -0
  86. package/lib/node/filesystem-watcher-service-impl.d.ts.map +1 -0
  87. package/lib/node/filesystem-watcher-service-impl.js +92 -0
  88. package/lib/node/filesystem-watcher-service-impl.js.map +1 -0
  89. package/lib/node/filesystem-watcher-service-impl.spec.d.ts +2 -0
  90. package/lib/node/filesystem-watcher-service-impl.spec.d.ts.map +1 -0
  91. package/lib/node/filesystem-watcher-service-impl.spec.js +202 -0
  92. package/lib/node/filesystem-watcher-service-impl.spec.js.map +1 -0
  93. package/lib/node/filesystem-watcher.d.ts +93 -0
  94. package/lib/node/filesystem-watcher.d.ts.map +1 -0
  95. package/lib/node/filesystem-watcher.js +134 -0
  96. package/lib/node/filesystem-watcher.js.map +1 -0
  97. package/lib/node/nodejs-watcher/node-directory-watcher.d.ts +175 -0
  98. package/lib/node/nodejs-watcher/node-directory-watcher.d.ts.map +1 -0
  99. package/lib/node/nodejs-watcher/node-directory-watcher.js +580 -0
  100. package/lib/node/nodejs-watcher/node-directory-watcher.js.map +1 -0
  101. package/lib/node/nodejs-watcher/node-directory-watcher.spec.d.ts +2 -0
  102. package/lib/node/nodejs-watcher/node-directory-watcher.spec.d.ts.map +1 -0
  103. package/lib/node/nodejs-watcher/node-directory-watcher.spec.js +1284 -0
  104. package/lib/node/nodejs-watcher/node-directory-watcher.spec.js.map +1 -0
  105. package/lib/node/nodejs-watcher/watch-request-router.d.ts +63 -0
  106. package/lib/node/nodejs-watcher/watch-request-router.d.ts.map +1 -0
  107. package/lib/node/nodejs-watcher/watch-request-router.js +183 -0
  108. package/lib/node/nodejs-watcher/watch-request-router.js.map +1 -0
  109. package/lib/node/nodejs-watcher/watcher-host.d.ts +50 -0
  110. package/lib/node/nodejs-watcher/watcher-host.d.ts.map +1 -0
  111. package/lib/node/nodejs-watcher/watcher-host.js +99 -0
  112. package/lib/node/nodejs-watcher/watcher-host.js.map +1 -0
  113. package/lib/node/parcel-watcher/index.js +4 -3
  114. package/lib/node/parcel-watcher/index.js.map +1 -1
  115. package/lib/node/parcel-watcher/parcel-filesystem-service.d.ts +14 -194
  116. package/lib/node/parcel-watcher/parcel-filesystem-service.d.ts.map +1 -1
  117. package/lib/node/parcel-watcher/parcel-filesystem-service.js +7 -424
  118. package/lib/node/parcel-watcher/parcel-filesystem-service.js.map +1 -1
  119. package/lib/node/parcel-watcher/parcel-filesystem-watcher.spec.js +8 -7
  120. package/lib/node/parcel-watcher/parcel-filesystem-watcher.spec.js.map +1 -1
  121. package/lib/node/parcel-watcher/parcel-watcher-exclude.spec.js +8 -7
  122. package/lib/node/parcel-watcher/parcel-watcher-exclude.spec.js.map +1 -1
  123. package/lib/node/parcel-watcher/parcel-watcher-retry.spec.js +7 -6
  124. package/lib/node/parcel-watcher/parcel-watcher-retry.spec.js.map +1 -1
  125. package/lib/node/parcel-watcher/parcel-watcher.d.ts +165 -0
  126. package/lib/node/parcel-watcher/parcel-watcher.d.ts.map +1 -0
  127. package/lib/node/parcel-watcher/parcel-watcher.js +370 -0
  128. package/lib/node/parcel-watcher/parcel-watcher.js.map +1 -0
  129. package/lib/node/test/watcher-test-helper.d.ts +21 -0
  130. package/lib/node/test/watcher-test-helper.d.ts.map +1 -0
  131. package/lib/node/test/watcher-test-helper.js +56 -0
  132. package/lib/node/test/watcher-test-helper.js.map +1 -0
  133. package/lib/node/upload/node-file-upload-service.spec.js +4 -3
  134. package/lib/node/upload/node-file-upload-service.spec.js.map +1 -1
  135. package/package.json +7 -7
  136. package/src/browser/download/file-download-service.ts +0 -1
  137. package/src/browser/file-dialog/file-dialog-tree-filters-renderer.spec.ts +43 -0
  138. package/src/browser/file-dialog/file-dialog-tree-filters-renderer.tsx +3 -1
  139. package/src/browser/file-dialog/file-dialog.ts +11 -1
  140. package/src/browser/file-service-copy.spec.ts +226 -0
  141. package/src/browser/file-service.ts +5 -5
  142. package/src/common/filesystem-watcher-protocol.ts +4 -0
  143. package/src/electron-browser/file-dialog/electron-file-dialog-service.spec.ts +57 -0
  144. package/src/electron-browser/file-dialog/electron-file-dialog-service.ts +1 -1
  145. package/src/node/disk-file-system-provider.spec.ts +29 -0
  146. package/src/node/disk-file-system-provider.ts +5 -2
  147. package/src/node/download/file-download-cache.ts +1 -1
  148. package/src/node/download/file-download-handler.ts +0 -1
  149. package/src/node/filesystem-backend-module.ts +5 -4
  150. package/src/node/filesystem-watcher-service-impl.spec.ts +272 -0
  151. package/src/node/filesystem-watcher-service-impl.ts +109 -0
  152. package/src/node/filesystem-watcher.ts +197 -0
  153. package/src/node/nodejs-watcher/node-directory-watcher.spec.ts +1594 -0
  154. package/src/node/nodejs-watcher/node-directory-watcher.ts +664 -0
  155. package/src/node/nodejs-watcher/watch-request-router.ts +224 -0
  156. package/src/node/nodejs-watcher/watcher-host.ts +126 -0
  157. package/src/node/parcel-watcher/index.ts +2 -2
  158. package/src/node/parcel-watcher/parcel-filesystem-service.ts +14 -500
  159. package/src/node/parcel-watcher/parcel-filesystem-watcher.spec.ts +4 -4
  160. package/src/node/parcel-watcher/parcel-watcher-exclude.spec.ts +4 -4
  161. package/src/node/parcel-watcher/parcel-watcher-retry.spec.ts +3 -3
  162. package/src/node/parcel-watcher/parcel-watcher.ts +432 -0
  163. package/src/node/test/watcher-test-helper.ts +58 -0
@@ -0,0 +1,226 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 Sahil Gupta 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 { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
18
+ let disableJSDOM = enableJSDOM();
19
+
20
+ import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
21
+ FrontendApplicationConfigProvider.set({});
22
+
23
+ import { Disposable, URI } from '@theia/core';
24
+ import { expect } from 'chai';
25
+ import * as sinon from 'sinon';
26
+ import { FileSystemProvider, FileSystemProviderCapabilities } from '../common/files';
27
+ import { FileService } from './file-service';
28
+
29
+ disableJSDOM();
30
+
31
+ function createMockProvider(caseSensitive: boolean): FileSystemProvider {
32
+ return {
33
+ capabilities: caseSensitive ? FileSystemProviderCapabilities.PathCaseSensitive : 0,
34
+ onDidChangeCapabilities: () => Disposable.NULL,
35
+ onDidChangeFile: () => Disposable.NULL,
36
+ onFileWatchError: () => Disposable.NULL,
37
+ watch: () => Disposable.NULL,
38
+ stat: () => { throw new Error('not implemented'); },
39
+ readdir: () => { throw new Error('not implemented'); },
40
+ readFile: () => { throw new Error('not implemented'); },
41
+ writeFile: () => { throw new Error('not implemented'); },
42
+ delete: () => { throw new Error('not implemented'); },
43
+ mkdir: () => { throw new Error('not implemented'); },
44
+ rename: () => { throw new Error('not implemented'); },
45
+ copy: () => { throw new Error('not implemented'); },
46
+ } as unknown as FileSystemProvider;
47
+ }
48
+
49
+ /** Resolve to the rejection reason of the given promise, or to `undefined` if it fulfills. */
50
+ async function rejection(promise: Promise<unknown>): Promise<Error | undefined> {
51
+ try {
52
+ await promise;
53
+ return undefined;
54
+ } catch (error) {
55
+ return error as Error;
56
+ }
57
+ }
58
+
59
+ describe('FileService move/copy validation', () => {
60
+ const sandbox = sinon.createSandbox();
61
+ let fileService: FileService;
62
+ let provider: FileSystemProvider;
63
+ let existsStub: sinon.SinonStub;
64
+
65
+ before(() => {
66
+ disableJSDOM = enableJSDOM();
67
+ });
68
+
69
+ after(() => {
70
+ disableJSDOM();
71
+ });
72
+
73
+ afterEach(() => {
74
+ sandbox.restore();
75
+ });
76
+
77
+ /** Create a `FileService` backed by a single mock provider for both source and target. */
78
+ function setup(caseSensitive: boolean = true): void {
79
+ sandbox.restore();
80
+ fileService = new FileService();
81
+ provider = createMockProvider(caseSensitive);
82
+ /* eslint-disable @typescript-eslint/no-explicit-any */
83
+ sandbox.stub(fileService as any, 'withProvider').resolves(provider);
84
+ sandbox.stub(fileService as any, 'withReadProvider').resolves(provider);
85
+ sandbox.stub(fileService as any, 'withWriteProvider').resolves(provider);
86
+ // The error messages name the involved resources through the label provider.
87
+ (fileService as any).labelProvider = { getLongName: (resource: URI) => resource.path.toString() };
88
+ /* eslint-enable @typescript-eslint/no-explicit-any */
89
+ // By default the target does not exist yet, which is the case when pasting a folder into one of its own subfolders.
90
+ existsStub = sandbox.stub(fileService, 'exists').resolves(false);
91
+ }
92
+
93
+ function validate(source: string, target: string, mode: 'move' | 'copy'): Promise<unknown> {
94
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
+ return (fileService as any).doValidateMoveCopy(provider, new URI(source), provider, new URI(target), mode, false);
96
+ }
97
+
98
+ describe('source containing the target', () => {
99
+
100
+ it('should reject copying a folder into a subfolder of itself', async () => {
101
+ setup();
102
+ const error = await rejection(validate('file:///a', 'file:///a/b/a', 'copy'));
103
+ expect(error, 'copying a folder into its own subfolder was allowed').to.be.an.instanceOf(Error);
104
+ expect(error!.message).to.contain('is parent of target');
105
+ });
106
+
107
+ it('should reject moving a folder into a subfolder of itself', async () => {
108
+ setup();
109
+ const error = await rejection(validate('file:///a', 'file:///a/b', 'move'));
110
+ expect(error, 'moving a folder into its own subfolder was allowed').to.be.an.instanceOf(Error);
111
+ expect(error!.message).to.contain('is parent of target');
112
+ });
113
+
114
+ it('should reject a deeply nested target inside the source', async () => {
115
+ setup();
116
+ const error = await rejection(validate('file:///a', 'file:///a/b/c/d/a', 'copy'));
117
+ expect(error, 'copying a folder deep into its own subtree was allowed').to.be.an.instanceOf(Error);
118
+ expect(error!.message).to.contain('is parent of target');
119
+ });
120
+
121
+ it('should reject copying a folder into its own subfolder through FileService.copy', async () => {
122
+ setup();
123
+ const error = await rejection(fileService.copy(new URI('file:///a'), new URI('file:///a/b/a'), { fromUserGesture: false }));
124
+ expect(error, 'FileService.copy allowed a copy into the source subtree').to.be.an.instanceOf(Error);
125
+ expect(error!.message).to.contain('is parent of target');
126
+ });
127
+
128
+ it('should reject moving a folder into its own subfolder through FileService.move', async () => {
129
+ setup();
130
+ const error = await rejection(fileService.move(new URI('file:///a'), new URI('file:///a/b'), { fromUserGesture: false }));
131
+ expect(error, 'FileService.move allowed a move into the source subtree').to.be.an.instanceOf(Error);
132
+ expect(error!.message).to.contain('is parent of target');
133
+ });
134
+
135
+ it('should reject a target inside the source that only differs in case on a case-insensitive provider', async () => {
136
+ setup(false);
137
+ const error = await rejection(validate('file:///a', 'file:///A/b/a', 'copy'));
138
+ expect(error, 'a case-insensitive provider allowed a copy into the source subtree').to.be.an.instanceOf(Error);
139
+ expect(error!.message).to.contain('is parent of target');
140
+ });
141
+ });
142
+
143
+ describe('the same resource', () => {
144
+
145
+ it('should reject copying a resource onto itself', async () => {
146
+ setup();
147
+ const error = await rejection(validate('file:///a/b', 'file:///a/b', 'copy'));
148
+ expect(error, 'copying a resource onto itself was allowed').to.be.an.instanceOf(Error);
149
+ expect(error!.message).to.contain('is parent of target');
150
+ });
151
+
152
+ it('should reject moving a resource onto itself', async () => {
153
+ setup();
154
+ const error = await rejection(validate('file:///a/b', 'file:///a/b', 'move'));
155
+ expect(error, 'moving a resource onto itself was allowed').to.be.an.instanceOf(Error);
156
+ expect(error!.message).to.contain('is parent of target');
157
+ });
158
+ });
159
+
160
+ describe('unrelated and adjacent targets', () => {
161
+
162
+ it('should accept an unrelated target', async () => {
163
+ setup();
164
+ const result = await validate('file:///a', 'file:///c/a', 'copy');
165
+ expect(result).to.deep.equal({ exists: false, isSameResourceWithDifferentPathCase: false });
166
+ });
167
+
168
+ it('should accept a target whose path merely shares a prefix with the source', async () => {
169
+ setup();
170
+ const result = await validate('file:///a', 'file:///ab/a', 'copy');
171
+ expect(result).to.deep.equal({ exists: false, isSameResourceWithDifferentPathCase: false });
172
+ });
173
+
174
+ it('should accept a target in a different file system', async () => {
175
+ setup();
176
+ const result = await validate('file:///a', 'memory:///a/b/a', 'copy');
177
+ expect(result).to.deep.equal({ exists: false, isSameResourceWithDifferentPathCase: false });
178
+ });
179
+ });
180
+
181
+ describe('the target containing the source', () => {
182
+
183
+ it('should reject moving a resource onto the existing folder that contains it', async () => {
184
+ setup();
185
+ existsStub.resolves(true);
186
+ const error = await rejection(validate('file:///a/b', 'file:///a', 'move'));
187
+ expect(error, 'moving a resource onto its own parent folder was allowed').to.be.an.instanceOf(Error);
188
+ expect(error!.message).to.contain('would replace the folder it is contained in');
189
+ });
190
+ });
191
+
192
+ describe('case-only renames on a case-insensitive provider', () => {
193
+
194
+ it('should accept a case-only rename', async () => {
195
+ setup(false);
196
+ const result = await validate('file:///a/Foo', 'file:///a/foo', 'move');
197
+ expect(result).to.deep.equal({ exists: false, isSameResourceWithDifferentPathCase: true });
198
+ });
199
+
200
+ it('should accept a case-only rename of a nested resource', async () => {
201
+ setup(false);
202
+ const result = await validate('file:///a/b/Foo.ts', 'file:///a/b/foo.ts', 'move');
203
+ expect(result).to.deep.equal({ exists: false, isSameResourceWithDifferentPathCase: true });
204
+ });
205
+
206
+ it('should accept a case-only rename even when the target is reported as existing', async () => {
207
+ setup(false);
208
+ existsStub.resolves(true);
209
+ const result = await validate('file:///a/Foo', 'file:///a/foo', 'move');
210
+ expect(result).to.deep.equal({ exists: true, isSameResourceWithDifferentPathCase: true });
211
+ });
212
+
213
+ it('should reject a case-only copy', async () => {
214
+ setup(false);
215
+ const error = await rejection(validate('file:///a/Foo', 'file:///a/foo', 'copy'));
216
+ expect(error, 'a case-only copy was allowed').to.be.an.instanceOf(Error);
217
+ expect(error!.message).to.contain('is parent of target');
218
+ });
219
+
220
+ it('should accept a case-only rename on a case-sensitive provider as a distinct resource', async () => {
221
+ setup(true);
222
+ const result = await validate('file:///a/Foo', 'file:///a/foo', 'move');
223
+ expect(result).to.deep.equal({ exists: false, isSameResourceWithDifferentPathCase: false });
224
+ });
225
+ });
226
+ });
@@ -24,9 +24,9 @@
24
24
  // and https://github.com/microsoft/vscode/blob/04c36be045a94fee58e5f8992d3e3fd980294a84/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts
25
25
 
26
26
  /* eslint-disable max-len */
27
+ /* eslint-disable @stylistic/brace-style, @stylistic/semi-spacing */
27
28
  /* eslint-disable @typescript-eslint/no-shadow */
28
29
  /* eslint-disable no-null/no-null */
29
- /* eslint-disable @typescript-eslint/tslint/config */
30
30
  /* eslint-disable @typescript-eslint/no-explicit-any */
31
31
 
32
32
  import { injectable, inject, named, postConstruct } from '@theia/core/shared/inversify';
@@ -201,7 +201,7 @@ export const FileServiceContribution = Symbol('FileServiceContribution');
201
201
  * });
202
202
  *
203
203
  * }
204
- *```
204
+ * ```
205
205
  */
206
206
  export interface FileServiceContribution {
207
207
  /**
@@ -515,7 +515,7 @@ export class FileService {
515
515
  */
516
516
  resolve(resource: URI, options: ResolveMetadataFileOptions): Promise<FileStatWithMetadata>;
517
517
  resolve(resource: URI, options?: ResolveFileOptions | undefined): Promise<FileStat>;
518
- async resolve(resource: any, options?: any) {
518
+ async resolve(resource: any, options?: any): Promise<FileStat> {
519
519
  try {
520
520
  return await this.doResolveFile(resource, options);
521
521
  } catch (error) {
@@ -1282,7 +1282,7 @@ export class FileService {
1282
1282
  throw new Error(nls.localizeByDefault("Unable to move/copy when source '{0}' is parent of target '{1}'.", this.resourceForError(source), this.resourceForError(target)));
1283
1283
  }
1284
1284
 
1285
- if (!isSameResourceWithDifferentPathCase && target.isEqualOrParent(source, isPathCaseSensitive)) {
1285
+ if (!isSameResourceWithDifferentPathCase && source.isEqualOrParent(target, isPathCaseSensitive)) {
1286
1286
  throw new Error(nls.localizeByDefault("Unable to move/copy when source '{0}' is parent of target '{1}'.", this.resourceForError(source), this.resourceForError(target)));
1287
1287
  }
1288
1288
  }
@@ -1295,7 +1295,7 @@ export class FileService {
1295
1295
  // it as it would delete the source as well. In this case we have to throw
1296
1296
  if (sourceProvider === targetProvider) {
1297
1297
  const isPathCaseSensitive = !!(sourceProvider.capabilities & FileSystemProviderCapabilities.PathCaseSensitive);
1298
- if (source.isEqualOrParent(target, isPathCaseSensitive)) {
1298
+ if (target.isEqualOrParent(source, isPathCaseSensitive)) {
1299
1299
  throw new Error(nls.localizeByDefault("Unable to move/copy '{0}' into '{1}' since a file would replace the folder it is contained in.", this.resourceForError(source), this.resourceForError(target)));
1300
1300
  }
1301
1301
  }
@@ -89,6 +89,10 @@ export interface FileSystemWatcherClient {
89
89
 
90
90
  export interface WatchOptions {
91
91
  ignored: string[];
92
+ /**
93
+ * Watch the whole subtree under the given path. Defaults to `true`.
94
+ */
95
+ recursive?: boolean;
92
96
  }
93
97
  export interface FileChange {
94
98
  uri: string;
@@ -0,0 +1,57 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 Vinay Kaushal 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 { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
18
+ let disableJSDOM = enableJSDOM();
19
+
20
+ import { expect } from 'chai';
21
+ import { FileUri } from '@theia/core/lib/common/file-uri';
22
+ import URI from '@theia/core/lib/common/uri';
23
+ import { OpenFileDialogProps } from '../../browser/file-dialog';
24
+ import { OpenDialogOptions } from '../../electron-common/electron-api';
25
+ import { ElectronFileDialogService } from './electron-file-dialog-service';
26
+
27
+ disableJSDOM();
28
+
29
+ class TestElectronFileDialogService extends ElectronFileDialogService {
30
+ override toOpenDialogOptions(uri: URI, props: OpenFileDialogProps): OpenDialogOptions {
31
+ return super.toOpenDialogOptions(uri, props);
32
+ }
33
+ }
34
+
35
+ describe('ElectronFileDialogService', () => {
36
+
37
+ before(() => disableJSDOM = enableJSDOM());
38
+ after(() => disableJSDOM());
39
+
40
+ const uri = FileUri.create('/home/user');
41
+ const filters = { 'Theia Workspace': ['theia-workspace'] };
42
+
43
+ it('adds an All Files filter to the given filters', () => {
44
+ const options = new TestElectronFileDialogService().toOpenDialogOptions(uri, { title: 'Open', canSelectFiles: true, filters });
45
+ expect(options.filters).to.deep.equal([
46
+ { name: 'Theia Workspace', extensions: ['theia-workspace'] },
47
+ { name: 'All Files', extensions: ['*'] }
48
+ ]);
49
+ });
50
+
51
+ it('omits the All Files filter if it is disabled', () => {
52
+ const options = new TestElectronFileDialogService().toOpenDialogOptions(uri, { title: 'Open', canSelectFiles: true, filters, disableAllFilesFilter: true });
53
+ expect(options.filters).to.deep.equal([
54
+ { name: 'Theia Workspace', extensions: ['theia-workspace'] }
55
+ ]);
56
+ });
57
+ });
@@ -136,7 +136,7 @@ export class ElectronFileDialogService extends DefaultFileDialogService {
136
136
  result.filters.push({ name: label, extensions: extensions });
137
137
  }
138
138
 
139
- if (props.canSelectFiles) {
139
+ if (props.canSelectFiles && !props.disableAllFilesFilter) {
140
140
  if (filters.length > 0) {
141
141
  result.filters.push({ name: 'All Files', extensions: ['*'] });
142
142
  }
@@ -114,6 +114,35 @@ describe('disk-file-system-provider', () => {
114
114
  }
115
115
  });
116
116
 
117
+ it('handles errors during temporary cleanup', async () => {
118
+ const tempDirPath = tracked.mkdirSync();
119
+ const provider = fsProvider as unknown as {
120
+ rimrafMove(path: string): Promise<void>;
121
+ rimrafUnlink(path: string): Promise<void>;
122
+ logger: ILogger;
123
+ };
124
+ const originalRimrafUnlink = provider.rimrafUnlink;
125
+ const originalWarn = provider.logger.warn;
126
+ let resolveWarning!: () => void;
127
+ const warningHandled = new Promise<void>(resolve => {
128
+ resolveWarning = resolve;
129
+ });
130
+ provider.rimrafUnlink = async path => {
131
+ await fs.rm(path, { recursive: true, force: true });
132
+ throw new Error('Cleanup failed');
133
+ };
134
+ provider.logger.warn = async () => {
135
+ resolveWarning();
136
+ };
137
+ try {
138
+ await provider.rimrafMove(tempDirPath);
139
+ await warningHandled;
140
+ } finally {
141
+ provider.rimrafUnlink = originalRimrafUnlink;
142
+ provider.logger.warn = originalWarn;
143
+ }
144
+ });
145
+
117
146
  it('delete is able to delete file', async function (): Promise<void> {
118
147
  this.timeout(10000);
119
148
  // Place the temp dir under $HOME so the XDG trash dir (~/.local/share/Trash) is on the same
@@ -541,7 +541,9 @@ export class DiskFileSystemProvider implements Disposable,
541
541
  }
542
542
 
543
543
  // Delete but do not return as promise
544
- this.rimrafUnlink(pathInTemp);
544
+ this.rimrafUnlink(pathInTemp).catch(error => {
545
+ this.logger.warn(`Failed to clean up temporary rimraf folder '${pathInTemp}':`, error);
546
+ });
545
547
  } catch (error) {
546
548
  if (error.code !== 'ENOENT') {
547
549
  throw error;
@@ -833,7 +835,8 @@ export class DiskFileSystemProvider implements Disposable,
833
835
  };
834
836
  watcherService.watchFileChanges(resource.toString(), {
835
837
  // Convert from `files.WatchOptions` to internal `watcher-protocol.WatchOptions`:
836
- ignored: opts.excludes
838
+ ignored: opts.excludes,
839
+ recursive: opts.recursive
837
840
  }).then(watcherId => {
838
841
  if (handle.disposed) {
839
842
  watcherService.unwatchFileChanges(watcherId);
@@ -39,7 +39,7 @@ export class FileDownloadCache {
39
39
  downloadInfo.root = encodeURIComponent(downloadInfo.root);
40
40
  }
41
41
  // expires in 1 minute enough for parallel connections to be connected.
42
- downloadInfo.expire = Date.now() + (this.expireTimeInMinutes * 600000);
42
+ downloadInfo.expire = Date.now() + (this.expireTimeInMinutes * 60 * 1000);
43
43
  this.downloads.set(id, downloadInfo);
44
44
  }
45
45
 
@@ -285,7 +285,6 @@ export class MultiFileDownloadHandler extends FileDownloadHandler {
285
285
  }
286
286
  const options: PrepareDownloadOptions = { filePath: '', downloadId, remove: true, root: outputRootPath };
287
287
  if (tarPaths.length === 1) {
288
- // tslint:disable-next-line:whitespace
289
288
  const [outputPath,] = tarPaths;
290
289
  options.filePath = outputPath;
291
290
  await this.prepareDownload(request, response, options);
@@ -19,7 +19,8 @@ import { ContainerModule, interfaces } from '@theia/core/shared/inversify';
19
19
  import { ConnectionHandler, RpcConnectionHandler, ILogger } from '@theia/core/lib/common';
20
20
  import { FileSystemWatcherServer, FileSystemWatcherService } from '../common/filesystem-watcher-protocol';
21
21
  import { FileSystemWatcherServerClient } from './filesystem-watcher-client';
22
- import { ParcelFileSystemWatcherService, ParcelFileSystemWatcherServerOptions } from './parcel-watcher/parcel-filesystem-service';
22
+ import { FileSystemWatcherServiceImpl } from './filesystem-watcher-service-impl';
23
+ import { ParcelFileSystemWatcherServerOptions } from './parcel-watcher/parcel-watcher';
23
24
  import { NodeFileUploadService } from './upload/node-file-upload-service';
24
25
  import { ParcelWatcherOptions } from './parcel-watcher/parcel-options';
25
26
  import { DiskFileSystemProvider } from './disk-file-system-provider';
@@ -38,11 +39,11 @@ export const WATCHER_VERBOSE = process.argv.includes('--watcher-verbose');
38
39
 
39
40
  export const FileSystemWatcherServiceProcessOptions = Symbol('FileSystemWatcherServiceProcessOptions');
40
41
  /**
41
- * Options to control the way the `ParcelFileSystemWatcherService` process is spawned.
42
+ * Options to control the way the `FileSystemWatcherServiceImpl` process is spawned.
42
43
  */
43
44
  export interface FileSystemWatcherServiceProcessOptions {
44
45
  /**
45
- * Path to the script that will run the `ParcelFileSystemWatcherService` in a new process.
46
+ * Path to the script that will run the `FileSystemWatcherServiceImpl` in a new process.
46
47
  */
47
48
  entryPoint: string;
48
49
  }
@@ -102,7 +103,7 @@ export function bindFileSystemWatcherServer(bind: interfaces.Bind): void {
102
103
  export function createParcelFileSystemWatcherService(ctx: interfaces.Context): FileSystemWatcherService {
103
104
  const options = ctx.container.get<ParcelFileSystemWatcherServerOptions>(ParcelFileSystemWatcherServerOptions);
104
105
  const dispatcher = ctx.container.get<FileSystemWatcherServiceDispatcher>(FileSystemWatcherServiceDispatcher);
105
- const server = new ParcelFileSystemWatcherService(options);
106
+ const server = new FileSystemWatcherServiceImpl(options);
106
107
  server.setClient(dispatcher);
107
108
  return server;
108
109
  }