@theia/plugin-ext 1.73.0-next.2 → 1.73.0-next.24
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/hosted/node/hosted-plugin-localization-service.d.ts +2 -1
- package/lib/hosted/node/hosted-plugin-localization-service.d.ts.map +1 -1
- package/lib/hosted/node/hosted-plugin-localization-service.js +6 -1
- package/lib/hosted/node/hosted-plugin-localization-service.js.map +1 -1
- package/lib/hosted/node/plugin-deployer-handler-impl.js +4 -3
- package/lib/hosted/node/plugin-deployer-handler-impl.js.map +1 -1
- package/lib/hosted/node/scanners/scanner-theia.d.ts +2 -0
- package/lib/hosted/node/scanners/scanner-theia.d.ts.map +1 -1
- package/lib/hosted/node/scanners/scanner-theia.js +63 -50
- package/lib/hosted/node/scanners/scanner-theia.js.map +1 -1
- package/lib/main/browser/custom-editors/custom-editor-opener.d.ts.map +1 -1
- package/lib/main/browser/custom-editors/custom-editor-opener.js +1 -0
- package/lib/main/browser/custom-editors/custom-editor-opener.js.map +1 -1
- package/lib/main/browser/custom-editors/custom-editor-widget.d.ts +1 -0
- package/lib/main/browser/custom-editors/custom-editor-widget.d.ts.map +1 -1
- package/lib/main/browser/custom-editors/custom-editor-widget.js +3 -0
- package/lib/main/browser/custom-editors/custom-editor-widget.js.map +1 -1
- package/lib/main/browser/custom-editors/custom-editors-main.d.ts.map +1 -1
- package/lib/main/browser/custom-editors/custom-editors-main.js +2 -0
- package/lib/main/browser/custom-editors/custom-editors-main.js.map +1 -1
- package/lib/main/browser/debug/plugin-debug-service.d.ts +2 -1
- package/lib/main/browser/debug/plugin-debug-service.d.ts.map +1 -1
- package/lib/main/browser/debug/plugin-debug-service.js +8 -3
- package/lib/main/browser/debug/plugin-debug-service.js.map +1 -1
- package/lib/main/browser/languages-main.d.ts +2 -0
- package/lib/main/browser/languages-main.d.ts.map +1 -1
- package/lib/main/browser/languages-main.js +7 -1
- package/lib/main/browser/languages-main.js.map +1 -1
- 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/lib/main/browser/plugin-contribution-handler.d.ts +2 -0
- package/lib/main/browser/plugin-contribution-handler.d.ts.map +1 -1
- package/lib/main/browser/plugin-contribution-handler.js +10 -4
- package/lib/main/browser/plugin-contribution-handler.js.map +1 -1
- package/lib/main/browser/view/plugin-view-registry.d.ts +7 -0
- package/lib/main/browser/view/plugin-view-registry.d.ts.map +1 -1
- package/lib/main/browser/view/plugin-view-registry.js +85 -6
- package/lib/main/browser/view/plugin-view-registry.js.map +1 -1
- package/lib/main/browser/view/plugin-view-registry.spec.d.ts +2 -0
- package/lib/main/browser/view/plugin-view-registry.spec.d.ts.map +1 -0
- package/lib/main/browser/view/plugin-view-registry.spec.js +80 -0
- package/lib/main/browser/view/plugin-view-registry.spec.js.map +1 -0
- package/lib/main/browser/webview/webview-resource-cache.d.ts +2 -0
- package/lib/main/browser/webview/webview-resource-cache.d.ts.map +1 -1
- package/lib/main/browser/webview/webview-resource-cache.js +7 -1
- package/lib/main/browser/webview/webview-resource-cache.js.map +1 -1
- package/lib/main/browser/webview/webview.d.ts +2 -1
- package/lib/main/browser/webview/webview.d.ts.map +1 -1
- package/lib/main/browser/webview/webview.js +7 -1
- package/lib/main/browser/webview/webview.js.map +1 -1
- package/lib/main/node/plugin-deployer-impl.js +2 -1
- package/lib/main/node/plugin-deployer-impl.js.map +1 -1
- package/lib/plugin/languages/diagnostics.d.ts +4 -1
- package/lib/plugin/languages/diagnostics.d.ts.map +1 -1
- package/lib/plugin/languages/diagnostics.js +22 -9
- package/lib/plugin/languages/diagnostics.js.map +1 -1
- package/package.json +29 -29
- package/src/hosted/node/hosted-plugin-localization-service.ts +6 -3
- package/src/hosted/node/plugin-deployer-handler-impl.ts +5 -5
- package/src/hosted/node/scanners/scanner-theia.ts +63 -51
- package/src/main/browser/custom-editors/custom-editor-opener.tsx +1 -0
- package/src/main/browser/custom-editors/custom-editor-widget.ts +4 -0
- package/src/main/browser/custom-editors/custom-editors-main.ts +2 -0
- package/src/main/browser/debug/plugin-debug-service.ts +7 -5
- package/src/main/browser/languages-main.ts +6 -2
- 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
- package/src/main/browser/plugin-contribution-handler.ts +8 -4
- package/src/main/browser/view/plugin-view-registry.spec.ts +105 -0
- package/src/main/browser/view/plugin-view-registry.ts +91 -6
- package/src/main/browser/webview/webview-resource-cache.ts +6 -2
- package/src/main/browser/webview/webview.ts +6 -3
- package/src/main/node/plugin-deployer-impl.ts +2 -2
- package/src/plugin/languages/diagnostics.ts +28 -10
|
@@ -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) {
|
|
@@ -52,6 +52,7 @@ import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
|
|
|
52
52
|
import { LanguageService } from '@theia/core/lib/browser/language-service';
|
|
53
53
|
import { ThemeIcon } from '@theia/monaco-editor-core/esm/vs/base/common/themables';
|
|
54
54
|
import { JSONObject, JSONValue } from '@theia/core/shared/@lumino/coreutils';
|
|
55
|
+
import { ILogger } from '@theia/core';
|
|
55
56
|
|
|
56
57
|
// The enum export is missing from `vscode-textmate@9.2.0`
|
|
57
58
|
const enum StandardTokenType {
|
|
@@ -147,6 +148,9 @@ export class PluginContributionHandler {
|
|
|
147
148
|
@inject(ContextKeyService)
|
|
148
149
|
protected readonly contextKeyService: ContextKeyService;
|
|
149
150
|
|
|
151
|
+
@inject(ILogger) @named('plugin-ext:PluginContributionHandler')
|
|
152
|
+
protected readonly logger: ILogger;
|
|
153
|
+
|
|
150
154
|
protected readonly commandHandlers = new Map<string, CommandHandler['execute'] | undefined>();
|
|
151
155
|
|
|
152
156
|
protected readonly onDidRegisterCommandHandlerEmitter = new Emitter<string>();
|
|
@@ -164,8 +168,8 @@ export class PluginContributionHandler {
|
|
|
164
168
|
}
|
|
165
169
|
const toDispose = new DisposableCollection(Disposable.create(() => { /* mark as not disposed */ }));
|
|
166
170
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
167
|
-
const logError = (message: string, ...args: any[]) =>
|
|
168
|
-
const logWarning = (message: string, ...args: any[]) =>
|
|
171
|
+
const logError = (message: string, ...args: any[]) => this.logger.error(`[${clientId}][${plugin.metadata.model.id}]: ${message}`, ...args);
|
|
172
|
+
const logWarning = (message: string, ...args: any[]) => this.logger.warn(`[${clientId}][${plugin.metadata.model.id}]: ${message}`, ...args);
|
|
169
173
|
const pushContribution = (id: string, contribute: () => Disposable) => {
|
|
170
174
|
if (toDispose.disposed) {
|
|
171
175
|
return;
|
|
@@ -517,7 +521,7 @@ export class PluginContributionHandler {
|
|
|
517
521
|
|
|
518
522
|
registerCommand(command: Command, enablement?: string): Disposable {
|
|
519
523
|
if (this.hasCommand(command.id)) {
|
|
520
|
-
|
|
524
|
+
this.logger.warn(`command '${command.id}' already registered`);
|
|
521
525
|
return Disposable.NULL;
|
|
522
526
|
}
|
|
523
527
|
|
|
@@ -565,7 +569,7 @@ export class PluginContributionHandler {
|
|
|
565
569
|
|
|
566
570
|
registerCommandHandler(id: string, execute: CommandHandler['execute']): Disposable {
|
|
567
571
|
if (this.hasCommandHandler(id)) {
|
|
568
|
-
|
|
572
|
+
this.logger.warn(`command handler '${id}' already registered`);
|
|
569
573
|
return Disposable.NULL;
|
|
570
574
|
}
|
|
571
575
|
|
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
// PluginViewRegistry transitively imports browser widgets (Lumino) that touch `document` at load time.
|
|
19
|
+
const disableJSDOM = enableJSDOM();
|
|
20
|
+
|
|
21
|
+
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
|
|
22
|
+
// Some transitively imported modules read the frontend config at load time.
|
|
23
|
+
FrontendApplicationConfigProvider.set({});
|
|
24
|
+
|
|
25
|
+
import { expect } from 'chai';
|
|
26
|
+
import { Disposable } from '@theia/core/lib/common';
|
|
27
|
+
import { PluginViewRegistry, ViewContainerInfo } from './plugin-view-registry';
|
|
28
|
+
|
|
29
|
+
disableJSDOM();
|
|
30
|
+
|
|
31
|
+
interface RegisteredMenuAction {
|
|
32
|
+
commandId: string;
|
|
33
|
+
label: string;
|
|
34
|
+
when?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Minimal stand-in for {@link MenuModelRegistry} that records the menu actions registered for the
|
|
39
|
+
* "Views" menu and removes them again when the returned disposable is disposed. This lets us assert
|
|
40
|
+
* on the labels {@link PluginViewRegistry} ends up showing without wiring the full DI container.
|
|
41
|
+
*/
|
|
42
|
+
class RecordingMenuModelRegistry {
|
|
43
|
+
readonly actions = new Map<string, RegisteredMenuAction>();
|
|
44
|
+
|
|
45
|
+
registerMenuAction(_menuPath: unknown, action: RegisteredMenuAction): Disposable {
|
|
46
|
+
this.actions.set(action.commandId, { ...action });
|
|
47
|
+
return Disposable.create(() => this.actions.delete(action.commandId));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe('PluginViewRegistry - view menu labels', () => {
|
|
52
|
+
|
|
53
|
+
let registry: PluginViewRegistry;
|
|
54
|
+
let menus: RecordingMenuModelRegistry;
|
|
55
|
+
|
|
56
|
+
const toggleCommandId = (id: string): string => `plugin.view-container.${id}.toggle`;
|
|
57
|
+
const internals = (): {
|
|
58
|
+
viewContainers: Map<string, ViewContainerInfo>;
|
|
59
|
+
registerViewMenuAction(containerId: string, label: string): Disposable;
|
|
60
|
+
} => registry as unknown as {
|
|
61
|
+
viewContainers: Map<string, ViewContainerInfo>;
|
|
62
|
+
registerViewMenuAction(containerId: string, label: string): Disposable;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function registerContainer(id: string, label: string, location: string): Disposable {
|
|
66
|
+
internals().viewContainers.set(id, { id, location, options: { label }, onViewAdded: () => { } });
|
|
67
|
+
return internals().registerViewMenuAction(id, label);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
registry = new PluginViewRegistry();
|
|
72
|
+
menus = new RecordingMenuModelRegistry();
|
|
73
|
+
(registry as unknown as { menus: unknown }).menus = menus;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('uses the plain label for a single container', () => {
|
|
77
|
+
registerContainer('a', 'Claude Code', 'left');
|
|
78
|
+
expect(menus.actions.get(toggleCommandId('a'))?.label).to.equal('Claude Code');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('leaves distinct labels unsuffixed', () => {
|
|
82
|
+
registerContainer('a', 'Explorer', 'left');
|
|
83
|
+
registerContainer('b', 'Claude Code', 'right');
|
|
84
|
+
expect(menus.actions.get(toggleCommandId('a'))?.label).to.equal('Explorer');
|
|
85
|
+
expect(menus.actions.get(toggleCommandId('b'))?.label).to.equal('Claude Code');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('suffixes the location when two containers share a label', () => {
|
|
89
|
+
registerContainer('a', 'Claude Code', 'left');
|
|
90
|
+
registerContainer('b', 'Claude Code', 'right');
|
|
91
|
+
expect(menus.actions.get(toggleCommandId('a'))?.label).to.equal('Claude Code (Side Bar)');
|
|
92
|
+
expect(menus.actions.get(toggleCommandId('b'))?.label).to.equal('Claude Code (Secondary Side Bar)');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('drops the suffix from the remaining container once the duplicate is removed', () => {
|
|
96
|
+
registerContainer('a', 'Claude Code', 'left');
|
|
97
|
+
const disposeB = registerContainer('b', 'Claude Code', 'right');
|
|
98
|
+
|
|
99
|
+
disposeB.dispose();
|
|
100
|
+
|
|
101
|
+
expect(menus.actions.get(toggleCommandId('a'))?.label).to.equal('Claude Code');
|
|
102
|
+
expect(menus.actions.has(toggleCommandId('b'))).to.equal(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
});
|
|
@@ -111,6 +111,9 @@ export class PluginViewRegistry implements FrontendApplicationContribution {
|
|
|
111
111
|
private readonly viewClauseContexts = new Map<string, Set<string> | undefined>();
|
|
112
112
|
private readonly viewContainerClauseContexts = new Map<string, Set<string> | undefined>();
|
|
113
113
|
|
|
114
|
+
private readonly viewMenuLabelToContainerIds = new Map<string, Set<string>>();
|
|
115
|
+
private readonly viewMenuDisposables = new Map<string, Disposable>();
|
|
116
|
+
|
|
114
117
|
private readonly viewDataProviders = new Map<string, ViewDataProvider>();
|
|
115
118
|
private readonly viewDataState = new Map<string, object>();
|
|
116
119
|
|
|
@@ -374,13 +377,10 @@ export class PluginViewRegistry implements FrontendApplicationContribution {
|
|
|
374
377
|
}, {
|
|
375
378
|
execute: () => this.toggleViewContainer(id)
|
|
376
379
|
}));
|
|
377
|
-
toDispose.push(this.
|
|
378
|
-
commandId: toggleCommandId,
|
|
379
|
-
label: options.label,
|
|
380
|
-
when
|
|
381
|
-
}));
|
|
380
|
+
toDispose.push(this.registerViewMenuAction(id, options.label));
|
|
382
381
|
toDispose.push(this.quickView?.registerItem({
|
|
383
382
|
label: options.label,
|
|
383
|
+
description: this.getLocationDescription(location),
|
|
384
384
|
when,
|
|
385
385
|
open: async () => {
|
|
386
386
|
const widget = await this.openViewContainer(id);
|
|
@@ -408,6 +408,88 @@ export class PluginViewRegistry implements FrontendApplicationContribution {
|
|
|
408
408
|
return toDispose;
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
+
protected getLocationDescription(location: string | undefined): string | undefined {
|
|
412
|
+
switch (location) {
|
|
413
|
+
case 'left': return nls.localizeByDefault('Side Bar');
|
|
414
|
+
case 'right': return nls.localizeByDefault('Secondary Side Bar');
|
|
415
|
+
case 'bottom': return nls.localizeByDefault('Panel');
|
|
416
|
+
default: return undefined;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
protected getContainerLocation(viewContainerId: string): string | undefined {
|
|
421
|
+
if (PluginViewRegistry.BUILTIN_VIEW_CONTAINERS.has(viewContainerId)) {
|
|
422
|
+
return 'left';
|
|
423
|
+
}
|
|
424
|
+
return this.viewContainers.get(viewContainerId)?.location;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
protected getViewQuickPickDescription(viewContainerId: string): string | undefined {
|
|
428
|
+
const locationDescription = this.getLocationDescription(this.getContainerLocation(viewContainerId));
|
|
429
|
+
const containerLabel = this.viewContainers.get(viewContainerId)?.options.label;
|
|
430
|
+
if (locationDescription && containerLabel) {
|
|
431
|
+
return `${locationDescription} / ${containerLabel}`;
|
|
432
|
+
}
|
|
433
|
+
return locationDescription;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
protected registerViewMenuAction(containerId: string, label: string): Disposable {
|
|
437
|
+
let ids = this.viewMenuLabelToContainerIds.get(label);
|
|
438
|
+
if (!ids) {
|
|
439
|
+
ids = new Set();
|
|
440
|
+
this.viewMenuLabelToContainerIds.set(label, ids);
|
|
441
|
+
}
|
|
442
|
+
ids.add(containerId);
|
|
443
|
+
this.refreshViewMenuLabel(label);
|
|
444
|
+
return Disposable.create(() => {
|
|
445
|
+
const set = this.viewMenuLabelToContainerIds.get(label);
|
|
446
|
+
if (set) {
|
|
447
|
+
set.delete(containerId);
|
|
448
|
+
if (set.size === 0) {
|
|
449
|
+
this.viewMenuLabelToContainerIds.delete(label);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const disposable = this.viewMenuDisposables.get(containerId);
|
|
453
|
+
if (disposable) {
|
|
454
|
+
disposable.dispose();
|
|
455
|
+
this.viewMenuDisposables.delete(containerId);
|
|
456
|
+
}
|
|
457
|
+
this.refreshViewMenuLabel(label);
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
protected refreshViewMenuLabel(label: string): void {
|
|
462
|
+
const ids = this.viewMenuLabelToContainerIds.get(label);
|
|
463
|
+
if (!ids) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const useSuffix = ids.size > 1;
|
|
467
|
+
for (const id of ids) {
|
|
468
|
+
const existing = this.viewMenuDisposables.get(id);
|
|
469
|
+
if (existing) {
|
|
470
|
+
existing.dispose();
|
|
471
|
+
this.viewMenuDisposables.delete(id);
|
|
472
|
+
}
|
|
473
|
+
const containerInfo = this.viewContainers.get(id);
|
|
474
|
+
if (!containerInfo) {
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
let menuLabel = label;
|
|
478
|
+
if (useSuffix) {
|
|
479
|
+
const description = this.getLocationDescription(containerInfo.location);
|
|
480
|
+
if (description) {
|
|
481
|
+
menuLabel = `${label} (${description})`;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const disposable = this.menus.registerMenuAction(CommonMenus.VIEW_VIEWS, {
|
|
485
|
+
commandId: `plugin.view-container.${id}.toggle`,
|
|
486
|
+
label: menuLabel,
|
|
487
|
+
when: containerInfo.when
|
|
488
|
+
});
|
|
489
|
+
this.viewMenuDisposables.set(id, disposable);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
411
493
|
protected isViewContainerVisible(containerId: string): boolean {
|
|
412
494
|
const info = this.viewContainers.get(containerId);
|
|
413
495
|
if (!info) {
|
|
@@ -471,6 +553,7 @@ export class PluginViewRegistry implements FrontendApplicationContribution {
|
|
|
471
553
|
}
|
|
472
554
|
toDispose.push(this.quickView?.registerItem({
|
|
473
555
|
label: view.name,
|
|
556
|
+
description: this.getViewQuickPickDescription(viewContainerId),
|
|
474
557
|
when: view.when,
|
|
475
558
|
open: () => this.openView(view.id, { activate: true })
|
|
476
559
|
}));
|
|
@@ -560,7 +643,9 @@ export class PluginViewRegistry implements FrontendApplicationContribution {
|
|
|
560
643
|
});
|
|
561
644
|
return _pendingResolution;
|
|
562
645
|
},
|
|
563
|
-
show:
|
|
646
|
+
show: (preserveFocus: boolean) => {
|
|
647
|
+
this.openView(viewId, preserveFocus ? { reveal: true } : { activate: true });
|
|
648
|
+
}
|
|
564
649
|
};
|
|
565
650
|
|
|
566
651
|
const toDispose = this.onNewResolverRegistered(resolver => {
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
-
import { injectable } from '@theia/core/shared/inversify';
|
|
17
|
+
import { injectable, inject, named } from '@theia/core/shared/inversify';
|
|
18
18
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
|
19
19
|
import { MaybePromise } from '@theia/core/lib/common/types';
|
|
20
|
+
import { ILogger } from '@theia/core';
|
|
20
21
|
|
|
21
22
|
export interface WebviewResourceResponse {
|
|
22
23
|
eTag: string | undefined,
|
|
@@ -29,6 +30,9 @@ export interface WebviewResourceResponse {
|
|
|
29
30
|
@injectable()
|
|
30
31
|
export class WebviewResourceCache {
|
|
31
32
|
|
|
33
|
+
@inject(ILogger) @named('plugin-ext:WebviewResourceCache')
|
|
34
|
+
protected readonly logger: ILogger;
|
|
35
|
+
|
|
32
36
|
protected readonly cache = new Deferred<Cache | undefined>();
|
|
33
37
|
|
|
34
38
|
constructor() {
|
|
@@ -39,7 +43,7 @@ export class WebviewResourceCache {
|
|
|
39
43
|
try {
|
|
40
44
|
this.cache.resolve(await caches.open('webview:v1'));
|
|
41
45
|
} catch (e) {
|
|
42
|
-
|
|
46
|
+
this.logger.error('Failed to enable webview caching: ', e);
|
|
43
47
|
this.cache.resolve(undefined);
|
|
44
48
|
}
|
|
45
49
|
}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import * as mime from 'mime';
|
|
24
24
|
import { JSONExt } from '@theia/core/shared/@lumino/coreutils';
|
|
25
|
-
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
|
25
|
+
import { injectable, inject, postConstruct, named } from '@theia/core/shared/inversify';
|
|
26
26
|
import { WebviewPanelOptions, WebviewPortMapping } from '@theia/plugin';
|
|
27
27
|
import { BaseWidget, Message, codicon } from '@theia/core/lib/browser/widgets/widget';
|
|
28
28
|
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
|
@@ -49,7 +49,7 @@ import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
|
49
49
|
import { FileOperationError, FileOperationResult } from '@theia/filesystem/lib/common/files';
|
|
50
50
|
import { BinaryBufferReadableStream } from '@theia/core/lib/common/buffer';
|
|
51
51
|
import { ExtractableWidget } from '@theia/core/lib/browser/widgets/extractable-widget';
|
|
52
|
-
import { MenuPath } from '@theia/core';
|
|
52
|
+
import { MenuPath, ILogger } from '@theia/core';
|
|
53
53
|
import { ContextMenuRenderer } from '@theia/core/lib/browser';
|
|
54
54
|
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
|
|
55
55
|
import { PluginViewWidget } from '../view/plugin-view-widget';
|
|
@@ -166,6 +166,9 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget, Extract
|
|
|
166
166
|
@inject(ContextKeyService)
|
|
167
167
|
protected readonly contextKeyService: ContextKeyService;
|
|
168
168
|
|
|
169
|
+
@inject(ILogger) @named('plugin-ext:WebviewWidget')
|
|
170
|
+
protected readonly logger: ILogger;
|
|
171
|
+
|
|
169
172
|
viewState: WebviewPanelViewState = {
|
|
170
173
|
visible: false,
|
|
171
174
|
active: false,
|
|
@@ -626,7 +629,7 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget, Extract
|
|
|
626
629
|
await this.ready.promise;
|
|
627
630
|
this.postMessage(channel, data);
|
|
628
631
|
} catch (e) {
|
|
629
|
-
|
|
632
|
+
this.logger.error(e);
|
|
630
633
|
}
|
|
631
634
|
}
|
|
632
635
|
|
|
@@ -42,7 +42,7 @@ export class PluginDeployerImpl implements PluginDeployer {
|
|
|
42
42
|
protected readonly onDidDeployEmitter = new Emitter<void>();
|
|
43
43
|
readonly onDidDeploy = this.onDidDeployEmitter.event;
|
|
44
44
|
|
|
45
|
-
@inject(ILogger)
|
|
45
|
+
@inject(ILogger) @named('plugin-ext:PluginDeployerImpl')
|
|
46
46
|
protected readonly logger: ILogger;
|
|
47
47
|
|
|
48
48
|
@inject(PluginDeployerHandler)
|
|
@@ -215,7 +215,7 @@ export class PluginDeployerImpl implements PluginDeployer {
|
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
} catch (e) {
|
|
218
|
-
|
|
218
|
+
this.logger.error(`Failed to resolve plugins from '${entry.id}'`, e);
|
|
219
219
|
errors.push(e instanceof Error ? e : new Error(String(e)));
|
|
220
220
|
}
|
|
221
221
|
}));
|
|
@@ -30,6 +30,7 @@ export class DiagnosticCollection implements theia.DiagnosticCollection {
|
|
|
30
30
|
];
|
|
31
31
|
|
|
32
32
|
private collectionName: string;
|
|
33
|
+
private ownerId: string;
|
|
33
34
|
private diagnosticsLimitPerResource: number;
|
|
34
35
|
private proxy: LanguagesMain;
|
|
35
36
|
private onDidChangeDiagnosticsEmitter: Emitter<theia.DiagnosticChangeEvent>;
|
|
@@ -38,8 +39,9 @@ export class DiagnosticCollection implements theia.DiagnosticCollection {
|
|
|
38
39
|
private isDisposed: boolean;
|
|
39
40
|
private onDisposeCallback: (() => void) | undefined;
|
|
40
41
|
|
|
41
|
-
constructor(name: string, maxCountPerFile: number, proxy: LanguagesMain, onDidChangeDiagnosticsEmitter: Emitter<theia.DiagnosticChangeEvent>) {
|
|
42
|
+
constructor(name: string, owner: string, maxCountPerFile: number, proxy: LanguagesMain, onDidChangeDiagnosticsEmitter: Emitter<theia.DiagnosticChangeEvent>) {
|
|
42
43
|
this.collectionName = name;
|
|
44
|
+
this.ownerId = owner;
|
|
43
45
|
this.diagnosticsLimitPerResource = maxCountPerFile;
|
|
44
46
|
this.proxy = proxy;
|
|
45
47
|
this.onDidChangeDiagnosticsEmitter = onDidChangeDiagnosticsEmitter;
|
|
@@ -53,6 +55,10 @@ export class DiagnosticCollection implements theia.DiagnosticCollection {
|
|
|
53
55
|
return this.collectionName;
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
get owner(): string {
|
|
59
|
+
return this.ownerId;
|
|
60
|
+
}
|
|
61
|
+
|
|
56
62
|
set(uri: theia.Uri, diagnostics: theia.Diagnostic[] | undefined): void;
|
|
57
63
|
set(entries: [theia.Uri, theia.Diagnostic[] | undefined][]): void;
|
|
58
64
|
set(arg: theia.Uri | [theia.Uri, theia.Diagnostic[] | undefined][], diagnostics?: theia.Diagnostic[] | undefined): void {
|
|
@@ -115,7 +121,7 @@ export class DiagnosticCollection implements theia.DiagnosticCollection {
|
|
|
115
121
|
if (this.has(uri)) {
|
|
116
122
|
this.fireDiagnosticChangeEvent(uri);
|
|
117
123
|
this.diagnostics.delete(uri.toString());
|
|
118
|
-
this.proxy.$changeDiagnostics(this.
|
|
124
|
+
this.proxy.$changeDiagnostics(this.ownerId, [[uri.toString(), []]]);
|
|
119
125
|
}
|
|
120
126
|
}
|
|
121
127
|
|
|
@@ -123,7 +129,7 @@ export class DiagnosticCollection implements theia.DiagnosticCollection {
|
|
|
123
129
|
this.ensureNotDisposed();
|
|
124
130
|
this.fireDiagnosticChangeEvent(this.getAllResourcesUris());
|
|
125
131
|
this.diagnostics.clear();
|
|
126
|
-
this.proxy.$clearDiagnostics(this.
|
|
132
|
+
this.proxy.$clearDiagnostics(this.ownerId);
|
|
127
133
|
}
|
|
128
134
|
|
|
129
135
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -247,7 +253,7 @@ export class DiagnosticCollection implements theia.DiagnosticCollection {
|
|
|
247
253
|
}
|
|
248
254
|
}
|
|
249
255
|
|
|
250
|
-
this.proxy.$changeDiagnostics(this.
|
|
256
|
+
this.proxy.$changeDiagnostics(this.ownerId, markers);
|
|
251
257
|
}
|
|
252
258
|
}
|
|
253
259
|
|
|
@@ -256,7 +262,12 @@ export class Diagnostics {
|
|
|
256
262
|
private static GENERATED_DIAGNOSTIC_COLLECTION_NAME_PREFIX = '_generated_diagnostic_collection_name_#';
|
|
257
263
|
|
|
258
264
|
private proxy: LanguagesMain;
|
|
259
|
-
|
|
265
|
+
// Collections are keyed by their unique `owner` id (which is the same as `name` for the first
|
|
266
|
+
// collection with that name, but is suffixed when a duplicate name is requested). This matches
|
|
267
|
+
// VS Code's behavior and prevents collisions on the main side's marker owner namespace as well
|
|
268
|
+
// as a latent bug where disposing the first collection would evict the second from the map.
|
|
269
|
+
private diagnosticCollections: Map<string, DiagnosticCollection>;
|
|
270
|
+
private nextOwnerSuffix = 1;
|
|
260
271
|
|
|
261
272
|
private diagnosticsChangedEmitter = new Emitter<theia.DiagnosticChangeEvent>();
|
|
262
273
|
public readonly onDidChangeDiagnostics: Event<theia.DiagnosticChangeEvent> = this.diagnosticsChangedEmitter.event;
|
|
@@ -278,19 +289,26 @@ export class Diagnostics {
|
|
|
278
289
|
}
|
|
279
290
|
|
|
280
291
|
createDiagnosticCollection(name?: string): theia.DiagnosticCollection {
|
|
292
|
+
let owner: string;
|
|
281
293
|
if (!name) {
|
|
282
294
|
do {
|
|
283
295
|
name = Diagnostics.GENERATED_DIAGNOSTIC_COLLECTION_NAME_PREFIX + this.getNextId();
|
|
284
296
|
} while (this.diagnosticCollections.has(name));
|
|
285
|
-
|
|
286
|
-
|
|
297
|
+
owner = name;
|
|
298
|
+
} else if (!this.diagnosticCollections.has(name)) {
|
|
299
|
+
owner = name;
|
|
300
|
+
} else {
|
|
301
|
+
console.warn(`Diagnostic collection with name '${name}' already exists.`);
|
|
302
|
+
do {
|
|
303
|
+
owner = name + this.nextOwnerSuffix++;
|
|
304
|
+
} while (this.diagnosticCollections.has(owner));
|
|
287
305
|
}
|
|
288
306
|
|
|
289
|
-
const diagnosticCollection = new DiagnosticCollection(name, Diagnostics.MAX_DIAGNOSTICS_PER_FILE, this.proxy, this.diagnosticsChangedEmitter);
|
|
307
|
+
const diagnosticCollection = new DiagnosticCollection(name, owner, Diagnostics.MAX_DIAGNOSTICS_PER_FILE, this.proxy, this.diagnosticsChangedEmitter);
|
|
290
308
|
diagnosticCollection.setOnDisposeCallback(() => {
|
|
291
|
-
this.diagnosticCollections.delete(
|
|
309
|
+
this.diagnosticCollections.delete(owner);
|
|
292
310
|
});
|
|
293
|
-
this.diagnosticCollections.set(
|
|
311
|
+
this.diagnosticCollections.set(owner, diagnosticCollection);
|
|
294
312
|
return diagnosticCollection;
|
|
295
313
|
}
|
|
296
314
|
|