@theia/plugin-ext 1.72.1 → 1.72.3
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/main/browser/main-file-system-event-service.d.ts +5 -1
- package/lib/main/browser/main-file-system-event-service.d.ts.map +1 -1
- package/lib/main/browser/main-file-system-event-service.js +21 -2
- package/lib/main/browser/main-file-system-event-service.js.map +1 -1
- package/lib/main/browser/main-file-system-event-service.spec.d.ts +2 -0
- package/lib/main/browser/main-file-system-event-service.spec.d.ts.map +1 -0
- package/lib/main/browser/main-file-system-event-service.spec.js +65 -0
- package/lib/main/browser/main-file-system-event-service.spec.js.map +1 -0
- package/package.json +30 -30
- package/src/main/browser/main-file-system-event-service.spec.ts +78 -0
- package/src/main/browser/main-file-system-event-service.ts +22 -2
|
@@ -2,15 +2,19 @@ import { interfaces } from '@theia/core/shared/inversify';
|
|
|
2
2
|
import { RPCProtocol } from '../../common/rpc-protocol';
|
|
3
3
|
import { MainFileSystemEventServiceShape } from '../../common/plugin-api-rpc';
|
|
4
4
|
import { UriComponents } from '../../common/uri-components';
|
|
5
|
+
import { URI } from '@theia/core';
|
|
5
6
|
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
6
7
|
import { WatchOptions } from '@theia/filesystem/lib/common/files';
|
|
8
|
+
import { FileSystemPreferences } from '@theia/filesystem/lib/common/filesystem-preferences';
|
|
7
9
|
export declare class MainFileSystemEventService implements MainFileSystemEventServiceShape {
|
|
8
10
|
private readonly fileService;
|
|
11
|
+
private readonly preferences;
|
|
9
12
|
private readonly toDispose;
|
|
10
13
|
private readonly watches;
|
|
11
|
-
constructor(rpc: RPCProtocol, container: interfaces.Container, fileService?: FileService);
|
|
14
|
+
constructor(rpc: RPCProtocol, container: interfaces.Container, fileService?: FileService, preferences?: FileSystemPreferences);
|
|
12
15
|
dispose(): void;
|
|
13
16
|
$watch(session: number, resource: UriComponents, options: WatchOptions): void;
|
|
17
|
+
protected getExcludes(uri: URI, requested?: string[]): string[];
|
|
14
18
|
$unwatch(session: number): void;
|
|
15
19
|
}
|
|
16
20
|
//# sourceMappingURL=main-file-system-event-service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main-file-system-event-service.d.ts","sourceRoot":"","sources":["../../../src/main/browser/main-file-system-event-service.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAsC,+BAA+B,EAAE,MAAM,6BAA6B,CAAC;AAClH,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"main-file-system-event-service.d.ts","sourceRoot":"","sources":["../../../src/main/browser/main-file-system-event-service.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAsC,+BAA+B,EAAE,MAAM,6BAA6B,CAAC;AAClH,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAElC,OAAO,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AACzE,OAAO,EAAkB,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClF,OAAO,EAAE,qBAAqB,EAAE,MAAM,qDAAqD,CAAC;AAE5F,qBAAa,0BAA2B,YAAW,+BAA+B;IAQ1E,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAPhC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;gBAGrD,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,UAAU,CAAC,SAAS,EACd,WAAW,cAA6B,EACxC,WAAW,wBAA8D;IAqC9F,OAAO,IAAI,IAAI;IAIf,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI;IAc7E,SAAS,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE;IAanE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAOlC"}
|
|
@@ -25,9 +25,11 @@ const plugin_api_rpc_1 = require("../../common/plugin-api-rpc");
|
|
|
25
25
|
const core_1 = require("@theia/core");
|
|
26
26
|
const disposable_1 = require("@theia/core/lib/common/disposable");
|
|
27
27
|
const file_service_1 = require("@theia/filesystem/lib/browser/file-service");
|
|
28
|
+
const filesystem_preferences_1 = require("@theia/filesystem/lib/common/filesystem-preferences");
|
|
28
29
|
class MainFileSystemEventService {
|
|
29
|
-
constructor(rpc, container, fileService = container.get(file_service_1.FileService)) {
|
|
30
|
+
constructor(rpc, container, fileService = container.get(file_service_1.FileService), preferences = container.get(filesystem_preferences_1.FileSystemPreferences)) {
|
|
30
31
|
this.fileService = fileService;
|
|
32
|
+
this.preferences = preferences;
|
|
31
33
|
this.toDispose = new disposable_1.DisposableCollection();
|
|
32
34
|
this.watches = new Map();
|
|
33
35
|
const proxy = rpc.getProxy(plugin_api_rpc_1.MAIN_RPC_CONTEXT.ExtHostFileSystemEventService);
|
|
@@ -67,10 +69,27 @@ class MainFileSystemEventService {
|
|
|
67
69
|
if (this.watches.has(session)) {
|
|
68
70
|
throw new Error(`There is already a watch request for the key ${session}`);
|
|
69
71
|
}
|
|
70
|
-
const
|
|
72
|
+
const uri = core_1.URI.fromComponents(resource);
|
|
73
|
+
// Plugin-created watchers (`vscode.workspace.createFileSystemWatcher`) arrive here with an
|
|
74
|
+
// empty `excludes` list. Language servers frequently request recursive watches rooted at
|
|
75
|
+
// absolute paths outside the workspace (e.g. JDT-LS's per-project globs), so apply the
|
|
76
|
+
// user's `files.watcherExclude` here to keep the number of OS file watches bounded.
|
|
77
|
+
const watch = this.fileService.watch(uri, { ...options, excludes: this.getExcludes(uri, options.excludes) });
|
|
71
78
|
this.toDispose.push(watch);
|
|
72
79
|
this.watches.set(session, watch);
|
|
73
80
|
}
|
|
81
|
+
getExcludes(uri, requested = []) {
|
|
82
|
+
const configured = this.preferences.get('files.watcherExclude', undefined, uri.toString());
|
|
83
|
+
const excludes = new Set(requested);
|
|
84
|
+
if (configured) {
|
|
85
|
+
for (const pattern of Object.keys(configured)) {
|
|
86
|
+
if (configured[pattern]) {
|
|
87
|
+
excludes.add(pattern);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return Array.from(excludes);
|
|
92
|
+
}
|
|
74
93
|
$unwatch(session) {
|
|
75
94
|
const watch = this.watches.get(session);
|
|
76
95
|
if (watch) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main-file-system-event-service.js","sourceRoot":"","sources":["../../../src/main/browser/main-file-system-event-service.ts"],"names":[],"mappings":";AAAA,gFAAgF;AAChF,yCAAyC;AACzC,EAAE;AACF,2EAA2E;AAC3E,mEAAmE;AACnE,wCAAwC;AACxC,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,yDAAyD;AACzD,uDAAuD;AACvD,EAAE;AACF,gFAAgF;AAChF,gFAAgF;AAChF;;;gGAGgG;AAChG,8JAA8J;;;AAI9J,gEAAkH;AAElH,sCAAkC;AAClC,kEAAqF;AACrF,6EAAyE;
|
|
1
|
+
{"version":3,"file":"main-file-system-event-service.js","sourceRoot":"","sources":["../../../src/main/browser/main-file-system-event-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main-file-system-event-service.spec.d.ts","sourceRoot":"","sources":["../../../src/main/browser/main-file-system-event-service.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// *****************************************************************************
|
|
3
|
+
// Copyright (C) 2026 Safi Seid-Ahmad, K2view and others.
|
|
4
|
+
//
|
|
5
|
+
// This program and the accompanying materials are made available under the
|
|
6
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
7
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
8
|
+
//
|
|
9
|
+
// This Source Code may also be made available under the following Secondary
|
|
10
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
11
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
12
|
+
// with the GNU Classpath Exception which is available at
|
|
13
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
14
|
+
//
|
|
15
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
16
|
+
// *****************************************************************************
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
const jsdom_1 = require("@theia/core/lib/browser/test/jsdom");
|
|
19
|
+
const disableJSDOM = (0, jsdom_1.enableJSDOM)();
|
|
20
|
+
const assert = require("assert");
|
|
21
|
+
const disposable_1 = require("@theia/core/lib/common/disposable");
|
|
22
|
+
const main_file_system_event_service_1 = require("./main-file-system-event-service");
|
|
23
|
+
disableJSDOM();
|
|
24
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
25
|
+
// A VS Code plugin (e.g. `redhat.java` hosting JDT-LS) that calls
|
|
26
|
+
// `vscode.workspace.createFileSystemWatcher` with a `RelativePattern` rooted at an absolute base
|
|
27
|
+
// triggers `ExtHostFileSystemEventService.ensureWatching`, which sends `$watch` to this main-side
|
|
28
|
+
// service with an EMPTY `excludes` list. As a result the user's `files.watcherExclude` preference
|
|
29
|
+
// was never applied to these (often sibling-of-workspace) recursive watches, so they crawled and
|
|
30
|
+
// watched whole external trees, exhausting the OS file-watch budget.
|
|
31
|
+
//
|
|
32
|
+
// This test pins the fix: the main side must merge the configured `files.watcherExclude` patterns
|
|
33
|
+
// into the watch options before delegating to the `FileService`.
|
|
34
|
+
describe('MainFileSystemEventService files.watcherExclude handling', () => {
|
|
35
|
+
function componentsFor(path) {
|
|
36
|
+
return { scheme: 'file', authority: '', path, query: '', fragment: '' };
|
|
37
|
+
}
|
|
38
|
+
it('applies files.watcherExclude to plugin-created watches that arrive with empty excludes', () => {
|
|
39
|
+
const watchCalls = [];
|
|
40
|
+
const fileService = {
|
|
41
|
+
onDidFilesChange: () => disposable_1.Disposable.NULL,
|
|
42
|
+
onDidRunUserOperation: () => disposable_1.Disposable.NULL,
|
|
43
|
+
addFileOperationParticipant: () => disposable_1.Disposable.NULL,
|
|
44
|
+
watch: (_resource, options) => {
|
|
45
|
+
watchCalls.push(options);
|
|
46
|
+
return disposable_1.Disposable.NULL;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const preferences = {
|
|
50
|
+
get: (preferenceName) => preferenceName === 'files.watcherExclude'
|
|
51
|
+
? { '**/node_modules/**': true, '**/.git/objects/**': true, '**/disabled-exclude/**': false }
|
|
52
|
+
: undefined
|
|
53
|
+
};
|
|
54
|
+
const rpc = { getProxy: () => ({}) };
|
|
55
|
+
const service = new main_file_system_event_service_1.MainFileSystemEventService(rpc, {}, fileService, preferences);
|
|
56
|
+
// Mirrors what `ensureWatching` sends for an absolute RelativePattern base outside the workspace.
|
|
57
|
+
service.$watch(1, componentsFor('/outside/workspace/storage'), { recursive: true, excludes: [] });
|
|
58
|
+
assert.strictEqual(watchCalls.length, 1, 'FileService.watch should have been called once');
|
|
59
|
+
const excludes = watchCalls[0].excludes;
|
|
60
|
+
assert.ok(excludes.includes('**/node_modules/**'), 'enabled watcherExclude pattern should be applied');
|
|
61
|
+
assert.ok(excludes.includes('**/.git/objects/**'), 'enabled watcherExclude pattern should be applied');
|
|
62
|
+
assert.ok(!excludes.includes('**/disabled-exclude/**'), 'patterns set to `false` must not be applied');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=main-file-system-event-service.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main-file-system-event-service.spec.js","sourceRoot":"","sources":["../../../src/main/browser/main-file-system-event-service.spec.ts"],"names":[],"mappings":";AAAA,gFAAgF;AAChF,yDAAyD;AACzD,EAAE;AACF,2EAA2E;AAC3E,mEAAmE;AACnE,wCAAwC;AACxC,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,yDAAyD;AACzD,uDAAuD;AACvD,EAAE;AACF,gFAAgF;AAChF,gFAAgF;;AAEhF,8DAAiE;AACjE,MAAM,YAAY,GAAG,IAAA,mBAAW,GAAE,CAAC;AAEnC,iCAAiC;AACjC,kEAA+D;AAG/D,qFAA8E;AAE9E,YAAY,EAAE,CAAC;AAEf,uDAAuD;AAEvD,kEAAkE;AAClE,iGAAiG;AACjG,kGAAkG;AAClG,kGAAkG;AAClG,iGAAiG;AACjG,qEAAqE;AACrE,EAAE;AACF,kGAAkG;AAClG,iEAAiE;AACjE,QAAQ,CAAC,0DAA0D,EAAE,GAAG,EAAE;IAEtE,SAAS,aAAa,CAAC,IAAY;QAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC5E,CAAC;IAED,EAAE,CAAC,wFAAwF,EAAE,GAAG,EAAE;QAC9F,MAAM,UAAU,GAAmB,EAAE,CAAC;QAEtC,MAAM,WAAW,GAAQ;YACrB,gBAAgB,EAAE,GAAG,EAAE,CAAC,uBAAU,CAAC,IAAI;YACvC,qBAAqB,EAAE,GAAG,EAAE,CAAC,uBAAU,CAAC,IAAI;YAC5C,2BAA2B,EAAE,GAAG,EAAE,CAAC,uBAAU,CAAC,IAAI;YAClD,KAAK,EAAE,CAAC,SAAc,EAAE,OAAqB,EAAE,EAAE;gBAC7C,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACzB,OAAO,uBAAU,CAAC,IAAI,CAAC;YAC3B,CAAC;SACJ,CAAC;QAEF,MAAM,WAAW,GAAQ;YACrB,GAAG,EAAE,CAAC,cAAsB,EAAE,EAAE,CAAC,cAAc,KAAK,sBAAsB;gBACtE,CAAC,CAAC,EAAE,oBAAoB,EAAE,IAAI,EAAE,oBAAoB,EAAE,IAAI,EAAE,wBAAwB,EAAE,KAAK,EAAE;gBAC7F,CAAC,CAAC,SAAS;SAClB,CAAC;QAEF,MAAM,GAAG,GAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAE1C,MAAM,OAAO,GAAG,IAAI,2DAA0B,CAAC,GAAG,EAAE,EAAS,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;QAEzF,kGAAkG;QAClG,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,aAAa,CAAC,4BAA4B,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;QAElG,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,gDAAgD,CAAC,CAAC;QAC3F,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QACxC,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,kDAAkD,CAAC,CAAC;QACvG,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,kDAAkD,CAAC,CAAC;QACvG,MAAM,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,6CAA6C,CAAC,CAAC;IAC3G,CAAC,CAAC,CAAC;AAEP,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theia/plugin-ext",
|
|
3
|
-
"version": "1.72.
|
|
3
|
+
"version": "1.72.3",
|
|
4
4
|
"description": "Theia - Plugin Extension",
|
|
5
5
|
"main": "lib/common/index.js",
|
|
6
6
|
"typings": "lib/common/index.d.ts",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@theia/ai-mcp": "1.72.
|
|
9
|
-
"@theia/bulk-edit": "1.72.
|
|
10
|
-
"@theia/callhierarchy": "1.72.
|
|
11
|
-
"@theia/console": "1.72.
|
|
12
|
-
"@theia/core": "1.72.
|
|
13
|
-
"@theia/debug": "1.72.
|
|
14
|
-
"@theia/editor": "1.72.
|
|
15
|
-
"@theia/editor-preview": "1.72.
|
|
16
|
-
"@theia/file-search": "1.72.
|
|
17
|
-
"@theia/filesystem": "1.72.
|
|
18
|
-
"@theia/markers": "1.72.
|
|
19
|
-
"@theia/messages": "1.72.
|
|
20
|
-
"@theia/monaco": "1.72.
|
|
8
|
+
"@theia/ai-mcp": "1.72.3",
|
|
9
|
+
"@theia/bulk-edit": "1.72.3",
|
|
10
|
+
"@theia/callhierarchy": "1.72.3",
|
|
11
|
+
"@theia/console": "1.72.3",
|
|
12
|
+
"@theia/core": "1.72.3",
|
|
13
|
+
"@theia/debug": "1.72.3",
|
|
14
|
+
"@theia/editor": "1.72.3",
|
|
15
|
+
"@theia/editor-preview": "1.72.3",
|
|
16
|
+
"@theia/file-search": "1.72.3",
|
|
17
|
+
"@theia/filesystem": "1.72.3",
|
|
18
|
+
"@theia/markers": "1.72.3",
|
|
19
|
+
"@theia/messages": "1.72.3",
|
|
20
|
+
"@theia/monaco": "1.72.3",
|
|
21
21
|
"@theia/monaco-editor-core": "1.108.201",
|
|
22
|
-
"@theia/navigator": "1.72.
|
|
23
|
-
"@theia/notebook": "1.72.
|
|
24
|
-
"@theia/output": "1.72.
|
|
25
|
-
"@theia/plugin": "1.72.
|
|
26
|
-
"@theia/preferences": "1.72.
|
|
27
|
-
"@theia/scm": "1.72.
|
|
28
|
-
"@theia/search-in-workspace": "1.72.
|
|
29
|
-
"@theia/task": "1.72.
|
|
30
|
-
"@theia/terminal": "1.72.
|
|
31
|
-
"@theia/test": "1.72.
|
|
32
|
-
"@theia/timeline": "1.72.
|
|
33
|
-
"@theia/typehierarchy": "1.72.
|
|
34
|
-
"@theia/variable-resolver": "1.72.
|
|
35
|
-
"@theia/workspace": "1.72.
|
|
22
|
+
"@theia/navigator": "1.72.3",
|
|
23
|
+
"@theia/notebook": "1.72.3",
|
|
24
|
+
"@theia/output": "1.72.3",
|
|
25
|
+
"@theia/plugin": "1.72.3",
|
|
26
|
+
"@theia/preferences": "1.72.3",
|
|
27
|
+
"@theia/scm": "1.72.3",
|
|
28
|
+
"@theia/search-in-workspace": "1.72.3",
|
|
29
|
+
"@theia/task": "1.72.3",
|
|
30
|
+
"@theia/terminal": "1.72.3",
|
|
31
|
+
"@theia/test": "1.72.3",
|
|
32
|
+
"@theia/timeline": "1.72.3",
|
|
33
|
+
"@theia/typehierarchy": "1.72.3",
|
|
34
|
+
"@theia/variable-resolver": "1.72.3",
|
|
35
|
+
"@theia/workspace": "1.72.3",
|
|
36
36
|
"@types/mime": "^2.0.3",
|
|
37
37
|
"@vscode/debugprotocol": "^1.68.0",
|
|
38
38
|
"@vscode/proxy-agent": "^0.13.2",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"watch": "theiaext watch"
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
92
|
-
"@theia/ext-scripts": "1.72.
|
|
92
|
+
"@theia/ext-scripts": "1.72.3",
|
|
93
93
|
"@types/decompress": "^4.2.7",
|
|
94
94
|
"@types/escape-html": "^0.0.20",
|
|
95
95
|
"@types/lodash.clonedeep": "^4.5.9"
|
|
@@ -97,5 +97,5 @@
|
|
|
97
97
|
"nyc": {
|
|
98
98
|
"extends": "../../configs/nyc.json"
|
|
99
99
|
},
|
|
100
|
-
"gitHead": "
|
|
100
|
+
"gitHead": "006945cdc227f743d0308704c27e259ed677d64f"
|
|
101
101
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 Safi Seid-Ahmad, K2view 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
|
+
const disableJSDOM = enableJSDOM();
|
|
19
|
+
|
|
20
|
+
import * as assert from 'assert';
|
|
21
|
+
import { Disposable } from '@theia/core/lib/common/disposable';
|
|
22
|
+
import { WatchOptions } from '@theia/filesystem/lib/common/files';
|
|
23
|
+
import { UriComponents } from '../../common/uri-components';
|
|
24
|
+
import { MainFileSystemEventService } from './main-file-system-event-service';
|
|
25
|
+
|
|
26
|
+
disableJSDOM();
|
|
27
|
+
|
|
28
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
29
|
+
|
|
30
|
+
// A VS Code plugin (e.g. `redhat.java` hosting JDT-LS) that calls
|
|
31
|
+
// `vscode.workspace.createFileSystemWatcher` with a `RelativePattern` rooted at an absolute base
|
|
32
|
+
// triggers `ExtHostFileSystemEventService.ensureWatching`, which sends `$watch` to this main-side
|
|
33
|
+
// service with an EMPTY `excludes` list. As a result the user's `files.watcherExclude` preference
|
|
34
|
+
// was never applied to these (often sibling-of-workspace) recursive watches, so they crawled and
|
|
35
|
+
// watched whole external trees, exhausting the OS file-watch budget.
|
|
36
|
+
//
|
|
37
|
+
// This test pins the fix: the main side must merge the configured `files.watcherExclude` patterns
|
|
38
|
+
// into the watch options before delegating to the `FileService`.
|
|
39
|
+
describe('MainFileSystemEventService files.watcherExclude handling', () => {
|
|
40
|
+
|
|
41
|
+
function componentsFor(path: string): UriComponents {
|
|
42
|
+
return { scheme: 'file', authority: '', path, query: '', fragment: '' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
it('applies files.watcherExclude to plugin-created watches that arrive with empty excludes', () => {
|
|
46
|
+
const watchCalls: WatchOptions[] = [];
|
|
47
|
+
|
|
48
|
+
const fileService: any = {
|
|
49
|
+
onDidFilesChange: () => Disposable.NULL,
|
|
50
|
+
onDidRunUserOperation: () => Disposable.NULL,
|
|
51
|
+
addFileOperationParticipant: () => Disposable.NULL,
|
|
52
|
+
watch: (_resource: any, options: WatchOptions) => {
|
|
53
|
+
watchCalls.push(options);
|
|
54
|
+
return Disposable.NULL;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const preferences: any = {
|
|
59
|
+
get: (preferenceName: string) => preferenceName === 'files.watcherExclude'
|
|
60
|
+
? { '**/node_modules/**': true, '**/.git/objects/**': true, '**/disabled-exclude/**': false }
|
|
61
|
+
: undefined
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const rpc: any = { getProxy: () => ({}) };
|
|
65
|
+
|
|
66
|
+
const service = new MainFileSystemEventService(rpc, {} as any, fileService, preferences);
|
|
67
|
+
|
|
68
|
+
// Mirrors what `ensureWatching` sends for an absolute RelativePattern base outside the workspace.
|
|
69
|
+
service.$watch(1, componentsFor('/outside/workspace/storage'), { recursive: true, excludes: [] });
|
|
70
|
+
|
|
71
|
+
assert.strictEqual(watchCalls.length, 1, 'FileService.watch should have been called once');
|
|
72
|
+
const excludes = watchCalls[0].excludes;
|
|
73
|
+
assert.ok(excludes.includes('**/node_modules/**'), 'enabled watcherExclude pattern should be applied');
|
|
74
|
+
assert.ok(excludes.includes('**/.git/objects/**'), 'enabled watcherExclude pattern should be applied');
|
|
75
|
+
assert.ok(!excludes.includes('**/disabled-exclude/**'), 'patterns set to `false` must not be applied');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
});
|
|
@@ -27,6 +27,7 @@ import { URI } from '@theia/core';
|
|
|
27
27
|
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
|
28
28
|
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
29
29
|
import { FileChangeType, WatchOptions } from '@theia/filesystem/lib/common/files';
|
|
30
|
+
import { FileSystemPreferences } from '@theia/filesystem/lib/common/filesystem-preferences';
|
|
30
31
|
|
|
31
32
|
export class MainFileSystemEventService implements MainFileSystemEventServiceShape {
|
|
32
33
|
|
|
@@ -36,7 +37,8 @@ export class MainFileSystemEventService implements MainFileSystemEventServiceSha
|
|
|
36
37
|
constructor(
|
|
37
38
|
rpc: RPCProtocol,
|
|
38
39
|
container: interfaces.Container,
|
|
39
|
-
private readonly fileService = container.get(FileService)
|
|
40
|
+
private readonly fileService = container.get(FileService),
|
|
41
|
+
private readonly preferences = container.get<FileSystemPreferences>(FileSystemPreferences)
|
|
40
42
|
) {
|
|
41
43
|
const proxy = rpc.getProxy(MAIN_RPC_CONTEXT.ExtHostFileSystemEventService);
|
|
42
44
|
|
|
@@ -81,11 +83,29 @@ export class MainFileSystemEventService implements MainFileSystemEventServiceSha
|
|
|
81
83
|
if (this.watches.has(session)) {
|
|
82
84
|
throw new Error(`There is already a watch request for the key ${session}`);
|
|
83
85
|
}
|
|
84
|
-
const
|
|
86
|
+
const uri = URI.fromComponents(resource);
|
|
87
|
+
// Plugin-created watchers (`vscode.workspace.createFileSystemWatcher`) arrive here with an
|
|
88
|
+
// empty `excludes` list. Language servers frequently request recursive watches rooted at
|
|
89
|
+
// absolute paths outside the workspace (e.g. JDT-LS's per-project globs), so apply the
|
|
90
|
+
// user's `files.watcherExclude` here to keep the number of OS file watches bounded.
|
|
91
|
+
const watch = this.fileService.watch(uri, { ...options, excludes: this.getExcludes(uri, options.excludes) });
|
|
85
92
|
this.toDispose.push(watch);
|
|
86
93
|
this.watches.set(session, watch);
|
|
87
94
|
}
|
|
88
95
|
|
|
96
|
+
protected getExcludes(uri: URI, requested: string[] = []): string[] {
|
|
97
|
+
const configured = this.preferences.get('files.watcherExclude', undefined, uri.toString());
|
|
98
|
+
const excludes = new Set(requested);
|
|
99
|
+
if (configured) {
|
|
100
|
+
for (const pattern of Object.keys(configured)) {
|
|
101
|
+
if (configured[pattern]) {
|
|
102
|
+
excludes.add(pattern);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return Array.from(excludes);
|
|
107
|
+
}
|
|
108
|
+
|
|
89
109
|
$unwatch(session: number): void {
|
|
90
110
|
const watch = this.watches.get(session);
|
|
91
111
|
if (watch) {
|