@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.
- package/lib/browser/download/file-download-service.d.ts.map +1 -1
- package/lib/browser/download/file-download-service.js +0 -1
- package/lib/browser/download/file-download-service.js.map +1 -1
- package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.d.ts +2 -0
- package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.d.ts.map +1 -1
- package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.js +1 -1
- package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.js.map +1 -1
- package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.spec.d.ts +2 -0
- package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.spec.d.ts.map +1 -0
- package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.spec.js +37 -0
- package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.spec.js.map +1 -0
- package/lib/browser/file-dialog/file-dialog.d.ts +5 -0
- package/lib/browser/file-dialog/file-dialog.d.ts.map +1 -1
- package/lib/browser/file-dialog/file-dialog.js +6 -2
- package/lib/browser/file-dialog/file-dialog.js.map +1 -1
- package/lib/browser/file-resource.spec.js +2 -1
- package/lib/browser/file-resource.spec.js.map +1 -1
- package/lib/browser/file-service-copy.spec.d.ts +2 -0
- package/lib/browser/file-service-copy.spec.d.ts.map +1 -0
- package/lib/browser/file-service-copy.spec.js +196 -0
- package/lib/browser/file-service-copy.spec.js.map +1 -0
- package/lib/browser/file-service-watcher.spec.js +2 -1
- package/lib/browser/file-service-watcher.spec.js.map +1 -1
- package/lib/browser/file-service.d.ts +1 -1
- package/lib/browser/file-service.js +3 -3
- package/lib/browser/file-service.js.map +1 -1
- package/lib/browser/file-tree/file-tree-decorator-adapter.js +1 -1
- package/lib/browser/file-tree/file-tree-decorator-adapter.js.map +1 -1
- package/lib/browser/file-tree/file-tree-decorator-adapter.spec.js +2 -1
- package/lib/browser/file-tree/file-tree-decorator-adapter.spec.js.map +1 -1
- package/lib/browser/file-tree/file-tree-model.js +1 -1
- package/lib/browser/file-tree/file-tree-model.js.map +1 -1
- package/lib/browser/file-tree/file-tree-widget.js +1 -1
- package/lib/browser/file-tree/file-tree-widget.js.map +1 -1
- package/lib/browser/filesystem-frontend-contribution.js +1 -1
- package/lib/browser/filesystem-frontend-contribution.js.map +1 -1
- package/lib/browser/location/location-renderer.js +1 -1
- package/lib/browser/location/location-renderer.js.map +1 -1
- package/lib/browser/location/location-renderer.spec.js +2 -1
- package/lib/browser/location/location-renderer.spec.js.map +1 -1
- package/lib/browser/upload/file-upload-service-impl.js +1 -1
- package/lib/browser/upload/file-upload-service-impl.js.map +1 -1
- package/lib/browser-only/download/file-download-service.js +1 -1
- package/lib/browser-only/download/file-download-service.js.map +1 -1
- package/lib/browser-only/file-search.js +2 -1
- package/lib/browser-only/file-search.js.map +1 -1
- package/lib/browser-only/upload/file-upload-service-impl.js +1 -1
- package/lib/browser-only/upload/file-upload-service-impl.js.map +1 -1
- package/lib/common/files.js +2 -1
- package/lib/common/files.js.map +1 -1
- package/lib/common/files.spec.js +2 -1
- package/lib/common/files.spec.js.map +1 -1
- package/lib/common/filesystem-watcher-protocol.d.ts +4 -0
- package/lib/common/filesystem-watcher-protocol.d.ts.map +1 -1
- package/lib/common/remote-file-system-provider.d.ts +4 -4
- package/lib/common/remote-file-system-provider.d.ts.map +1 -1
- package/lib/common/remote-file-system-provider.js +1 -1
- package/lib/common/remote-file-system-provider.js.map +1 -1
- package/lib/electron-browser/file-dialog/electron-file-dialog-service.js +1 -1
- package/lib/electron-browser/file-dialog/electron-file-dialog-service.js.map +1 -1
- package/lib/electron-browser/file-dialog/electron-file-dialog-service.spec.d.ts +2 -0
- package/lib/electron-browser/file-dialog/electron-file-dialog-service.spec.d.ts.map +1 -0
- package/lib/electron-browser/file-dialog/electron-file-dialog-service.spec.js +48 -0
- package/lib/electron-browser/file-dialog/electron-file-dialog-service.spec.js.map +1 -0
- package/lib/node/disk-file-system-provider.d.ts.map +1 -1
- package/lib/node/disk-file-system-provider.js +8 -5
- package/lib/node/disk-file-system-provider.js.map +1 -1
- package/lib/node/disk-file-system-provider.spec.js +28 -2
- package/lib/node/disk-file-system-provider.spec.js.map +1 -1
- package/lib/node/download/directory-archiver.js +2 -2
- package/lib/node/download/directory-archiver.js.map +1 -1
- package/lib/node/download/directory-archiver.spec.js +5 -4
- package/lib/node/download/directory-archiver.spec.js.map +1 -1
- package/lib/node/download/file-download-cache.js +1 -1
- package/lib/node/download/file-download-cache.js.map +1 -1
- package/lib/node/download/file-download-handler.d.ts.map +1 -1
- package/lib/node/download/file-download-handler.js +4 -5
- package/lib/node/download/file-download-handler.js.map +1 -1
- package/lib/node/file-change-collection.spec.js +2 -1
- package/lib/node/file-change-collection.spec.js.map +1 -1
- package/lib/node/filesystem-backend-module.d.ts +2 -2
- package/lib/node/filesystem-backend-module.d.ts.map +1 -1
- package/lib/node/filesystem-backend-module.js +7 -5
- package/lib/node/filesystem-backend-module.js.map +1 -1
- package/lib/node/filesystem-watcher-service-impl.d.ts +30 -0
- package/lib/node/filesystem-watcher-service-impl.d.ts.map +1 -0
- package/lib/node/filesystem-watcher-service-impl.js +92 -0
- package/lib/node/filesystem-watcher-service-impl.js.map +1 -0
- package/lib/node/filesystem-watcher-service-impl.spec.d.ts +2 -0
- package/lib/node/filesystem-watcher-service-impl.spec.d.ts.map +1 -0
- package/lib/node/filesystem-watcher-service-impl.spec.js +202 -0
- package/lib/node/filesystem-watcher-service-impl.spec.js.map +1 -0
- package/lib/node/filesystem-watcher.d.ts +93 -0
- package/lib/node/filesystem-watcher.d.ts.map +1 -0
- package/lib/node/filesystem-watcher.js +134 -0
- package/lib/node/filesystem-watcher.js.map +1 -0
- package/lib/node/nodejs-watcher/node-directory-watcher.d.ts +175 -0
- package/lib/node/nodejs-watcher/node-directory-watcher.d.ts.map +1 -0
- package/lib/node/nodejs-watcher/node-directory-watcher.js +580 -0
- package/lib/node/nodejs-watcher/node-directory-watcher.js.map +1 -0
- package/lib/node/nodejs-watcher/node-directory-watcher.spec.d.ts +2 -0
- package/lib/node/nodejs-watcher/node-directory-watcher.spec.d.ts.map +1 -0
- package/lib/node/nodejs-watcher/node-directory-watcher.spec.js +1284 -0
- package/lib/node/nodejs-watcher/node-directory-watcher.spec.js.map +1 -0
- package/lib/node/nodejs-watcher/watch-request-router.d.ts +63 -0
- package/lib/node/nodejs-watcher/watch-request-router.d.ts.map +1 -0
- package/lib/node/nodejs-watcher/watch-request-router.js +183 -0
- package/lib/node/nodejs-watcher/watch-request-router.js.map +1 -0
- package/lib/node/nodejs-watcher/watcher-host.d.ts +50 -0
- package/lib/node/nodejs-watcher/watcher-host.d.ts.map +1 -0
- package/lib/node/nodejs-watcher/watcher-host.js +99 -0
- package/lib/node/nodejs-watcher/watcher-host.js.map +1 -0
- package/lib/node/parcel-watcher/index.js +4 -3
- package/lib/node/parcel-watcher/index.js.map +1 -1
- package/lib/node/parcel-watcher/parcel-filesystem-service.d.ts +14 -194
- package/lib/node/parcel-watcher/parcel-filesystem-service.d.ts.map +1 -1
- package/lib/node/parcel-watcher/parcel-filesystem-service.js +7 -424
- package/lib/node/parcel-watcher/parcel-filesystem-service.js.map +1 -1
- package/lib/node/parcel-watcher/parcel-filesystem-watcher.spec.js +8 -7
- package/lib/node/parcel-watcher/parcel-filesystem-watcher.spec.js.map +1 -1
- package/lib/node/parcel-watcher/parcel-watcher-exclude.spec.js +8 -7
- package/lib/node/parcel-watcher/parcel-watcher-exclude.spec.js.map +1 -1
- package/lib/node/parcel-watcher/parcel-watcher-retry.spec.js +7 -6
- package/lib/node/parcel-watcher/parcel-watcher-retry.spec.js.map +1 -1
- package/lib/node/parcel-watcher/parcel-watcher.d.ts +165 -0
- package/lib/node/parcel-watcher/parcel-watcher.d.ts.map +1 -0
- package/lib/node/parcel-watcher/parcel-watcher.js +370 -0
- package/lib/node/parcel-watcher/parcel-watcher.js.map +1 -0
- package/lib/node/test/watcher-test-helper.d.ts +21 -0
- package/lib/node/test/watcher-test-helper.d.ts.map +1 -0
- package/lib/node/test/watcher-test-helper.js +56 -0
- package/lib/node/test/watcher-test-helper.js.map +1 -0
- package/lib/node/upload/node-file-upload-service.spec.js +4 -3
- package/lib/node/upload/node-file-upload-service.spec.js.map +1 -1
- package/package.json +7 -7
- package/src/browser/download/file-download-service.ts +0 -1
- package/src/browser/file-dialog/file-dialog-tree-filters-renderer.spec.ts +43 -0
- package/src/browser/file-dialog/file-dialog-tree-filters-renderer.tsx +3 -1
- package/src/browser/file-dialog/file-dialog.ts +11 -1
- package/src/browser/file-service-copy.spec.ts +226 -0
- package/src/browser/file-service.ts +5 -5
- package/src/common/filesystem-watcher-protocol.ts +4 -0
- package/src/electron-browser/file-dialog/electron-file-dialog-service.spec.ts +57 -0
- package/src/electron-browser/file-dialog/electron-file-dialog-service.ts +1 -1
- package/src/node/disk-file-system-provider.spec.ts +29 -0
- package/src/node/disk-file-system-provider.ts +5 -2
- package/src/node/download/file-download-cache.ts +1 -1
- package/src/node/download/file-download-handler.ts +0 -1
- package/src/node/filesystem-backend-module.ts +5 -4
- package/src/node/filesystem-watcher-service-impl.spec.ts +272 -0
- package/src/node/filesystem-watcher-service-impl.ts +109 -0
- package/src/node/filesystem-watcher.ts +197 -0
- package/src/node/nodejs-watcher/node-directory-watcher.spec.ts +1594 -0
- package/src/node/nodejs-watcher/node-directory-watcher.ts +664 -0
- package/src/node/nodejs-watcher/watch-request-router.ts +224 -0
- package/src/node/nodejs-watcher/watcher-host.ts +126 -0
- package/src/node/parcel-watcher/index.ts +2 -2
- package/src/node/parcel-watcher/parcel-filesystem-service.ts +14 -500
- package/src/node/parcel-watcher/parcel-filesystem-watcher.spec.ts +4 -4
- package/src/node/parcel-watcher/parcel-watcher-exclude.spec.ts +4 -4
- package/src/node/parcel-watcher/parcel-watcher-retry.spec.ts +3 -3
- package/src/node/parcel-watcher/parcel-watcher.ts +432 -0
- 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 &&
|
|
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 (
|
|
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 *
|
|
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 {
|
|
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 `
|
|
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 `
|
|
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
|
|
106
|
+
const server = new FileSystemWatcherServiceImpl(options);
|
|
106
107
|
server.setClient(dispatcher);
|
|
107
108
|
return server;
|
|
108
109
|
}
|