@theia/plugin-ext 1.73.0-next.3 → 1.73.0-next.39

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 (93) hide show
  1. package/lib/hosted/node/hosted-plugin-localization-service.d.ts +2 -1
  2. package/lib/hosted/node/hosted-plugin-localization-service.d.ts.map +1 -1
  3. package/lib/hosted/node/hosted-plugin-localization-service.js +6 -1
  4. package/lib/hosted/node/hosted-plugin-localization-service.js.map +1 -1
  5. package/lib/hosted/node/plugin-deployer-handler-impl.js +4 -3
  6. package/lib/hosted/node/plugin-deployer-handler-impl.js.map +1 -1
  7. package/lib/hosted/node/scanners/scanner-theia.d.ts +2 -0
  8. package/lib/hosted/node/scanners/scanner-theia.d.ts.map +1 -1
  9. package/lib/hosted/node/scanners/scanner-theia.js +63 -50
  10. package/lib/hosted/node/scanners/scanner-theia.js.map +1 -1
  11. package/lib/main/browser/comments/comment-thread-widget.d.ts +3 -3
  12. package/lib/main/browser/comments/comment-thread-widget.d.ts.map +1 -1
  13. package/lib/main/browser/comments/comment-thread-widget.js.map +1 -1
  14. package/lib/main/browser/custom-editors/custom-editor-opener.d.ts.map +1 -1
  15. package/lib/main/browser/custom-editors/custom-editor-opener.js +1 -0
  16. package/lib/main/browser/custom-editors/custom-editor-opener.js.map +1 -1
  17. package/lib/main/browser/custom-editors/custom-editor-widget.d.ts +1 -0
  18. package/lib/main/browser/custom-editors/custom-editor-widget.d.ts.map +1 -1
  19. package/lib/main/browser/custom-editors/custom-editor-widget.js +3 -0
  20. package/lib/main/browser/custom-editors/custom-editor-widget.js.map +1 -1
  21. package/lib/main/browser/custom-editors/custom-editors-main.d.ts.map +1 -1
  22. package/lib/main/browser/custom-editors/custom-editors-main.js +2 -0
  23. package/lib/main/browser/custom-editors/custom-editors-main.js.map +1 -1
  24. package/lib/main/browser/debug/debug-main.d.ts +1 -11
  25. package/lib/main/browser/debug/debug-main.d.ts.map +1 -1
  26. package/lib/main/browser/debug/debug-main.js +3 -24
  27. package/lib/main/browser/debug/debug-main.js.map +1 -1
  28. package/lib/main/browser/debug/plugin-debug-service.d.ts +2 -1
  29. package/lib/main/browser/debug/plugin-debug-service.d.ts.map +1 -1
  30. package/lib/main/browser/debug/plugin-debug-service.js +8 -3
  31. package/lib/main/browser/debug/plugin-debug-service.js.map +1 -1
  32. package/lib/main/browser/debug/plugin-debug-session-factory.d.ts +7 -44
  33. package/lib/main/browser/debug/plugin-debug-session-factory.d.ts.map +1 -1
  34. package/lib/main/browser/debug/plugin-debug-session-factory.js +29 -35
  35. package/lib/main/browser/debug/plugin-debug-session-factory.js.map +1 -1
  36. package/lib/main/browser/languages-main.d.ts +2 -0
  37. package/lib/main/browser/languages-main.d.ts.map +1 -1
  38. package/lib/main/browser/languages-main.js +7 -1
  39. package/lib/main/browser/languages-main.js.map +1 -1
  40. package/lib/main/browser/lm-main.js +2 -2
  41. package/lib/main/browser/lm-main.js.map +1 -1
  42. package/lib/main/browser/notebooks/renderers/cell-output-webview.d.ts +2 -1
  43. package/lib/main/browser/notebooks/renderers/cell-output-webview.d.ts.map +1 -1
  44. package/lib/main/browser/notebooks/renderers/cell-output-webview.js +11 -5
  45. package/lib/main/browser/notebooks/renderers/cell-output-webview.js.map +1 -1
  46. package/lib/main/browser/plugin-contribution-handler.d.ts +2 -0
  47. package/lib/main/browser/plugin-contribution-handler.d.ts.map +1 -1
  48. package/lib/main/browser/plugin-contribution-handler.js +10 -4
  49. package/lib/main/browser/plugin-contribution-handler.js.map +1 -1
  50. package/lib/main/browser/view/plugin-view-registry.d.ts +7 -0
  51. package/lib/main/browser/view/plugin-view-registry.d.ts.map +1 -1
  52. package/lib/main/browser/view/plugin-view-registry.js +85 -6
  53. package/lib/main/browser/view/plugin-view-registry.js.map +1 -1
  54. package/lib/main/browser/view/plugin-view-registry.spec.d.ts +2 -0
  55. package/lib/main/browser/view/plugin-view-registry.spec.d.ts.map +1 -0
  56. package/lib/main/browser/view/plugin-view-registry.spec.js +80 -0
  57. package/lib/main/browser/view/plugin-view-registry.spec.js.map +1 -0
  58. package/lib/main/browser/webview/webview-resource-cache.d.ts +2 -0
  59. package/lib/main/browser/webview/webview-resource-cache.d.ts.map +1 -1
  60. package/lib/main/browser/webview/webview-resource-cache.js +7 -1
  61. package/lib/main/browser/webview/webview-resource-cache.js.map +1 -1
  62. package/lib/main/browser/webview/webview.d.ts +2 -1
  63. package/lib/main/browser/webview/webview.d.ts.map +1 -1
  64. package/lib/main/browser/webview/webview.js +7 -1
  65. package/lib/main/browser/webview/webview.js.map +1 -1
  66. package/lib/main/node/plugin-deployer-impl.js +2 -1
  67. package/lib/main/node/plugin-deployer-impl.js.map +1 -1
  68. package/lib/plugin/languages/diagnostics.d.ts +4 -1
  69. package/lib/plugin/languages/diagnostics.d.ts.map +1 -1
  70. package/lib/plugin/languages/diagnostics.js +22 -9
  71. package/lib/plugin/languages/diagnostics.js.map +1 -1
  72. package/package.json +30 -30
  73. package/src/hosted/node/hosted-plugin-localization-service.ts +6 -3
  74. package/src/hosted/node/plugin-deployer-handler-impl.ts +5 -5
  75. package/src/hosted/node/scanners/scanner-theia.ts +63 -51
  76. package/src/main/browser/comments/comment-thread-widget.tsx +5 -5
  77. package/src/main/browser/custom-editors/custom-editor-opener.tsx +1 -0
  78. package/src/main/browser/custom-editors/custom-editor-widget.ts +4 -0
  79. package/src/main/browser/custom-editors/custom-editors-main.ts +2 -0
  80. package/src/main/browser/debug/debug-main.ts +3 -45
  81. package/src/main/browser/debug/plugin-debug-service.ts +7 -5
  82. package/src/main/browser/debug/plugin-debug-session-factory.ts +25 -71
  83. package/src/main/browser/languages-main.ts +6 -2
  84. package/src/main/browser/lm-main.ts +2 -2
  85. package/src/main/browser/notebooks/renderers/cell-output-webview.tsx +11 -7
  86. package/src/main/browser/plugin-contribution-handler.ts +8 -4
  87. package/src/main/browser/plugin-ext-widget.tsx +1 -1
  88. package/src/main/browser/view/plugin-view-registry.spec.ts +105 -0
  89. package/src/main/browser/view/plugin-view-registry.ts +91 -6
  90. package/src/main/browser/webview/webview-resource-cache.ts +6 -2
  91. package/src/main/browser/webview/webview.ts +6 -3
  92. package/src/main/node/plugin-deployer-impl.ts +2 -2
  93. package/src/plugin/languages/diagnostics.ts +28 -10
@@ -16,13 +16,13 @@
16
16
 
17
17
  import { DebuggerDescription, DebugPath, DebugService, DynamicDebugConfigurationProvider } from '@theia/debug/lib/common/debug-service';
18
18
  import debounce = require('@theia/core/shared/lodash.debounce');
19
- import { deepClone, Emitter, Event, nls } from '@theia/core';
19
+ import { deepClone, Emitter, Event, nls, ILogger } from '@theia/core';
20
20
  import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
21
21
  import { DebugConfiguration } from '@theia/debug/lib/common/debug-configuration';
22
22
  import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema';
23
23
  import { PluginDebugAdapterContribution } from './plugin-debug-adapter-contribution';
24
24
  import { PluginDebugConfigurationProvider } from './plugin-debug-configuration-provider';
25
- import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
25
+ import { injectable, inject, postConstruct, named } from '@theia/core/shared/inversify';
26
26
  import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider';
27
27
  import { WorkspaceService } from '@theia/workspace/lib/browser';
28
28
  import { CommandIdVariables } from '@theia/variable-resolver/lib/common/variable-types';
@@ -64,6 +64,8 @@ export class PluginDebugService implements DebugService {
64
64
  protected readonly connectionProvider: WebSocketConnectionProvider;
65
65
  @inject(WorkspaceService)
66
66
  protected readonly workspaceService: WorkspaceService;
67
+ @inject(ILogger) @named('plugin-ext:PluginDebugService')
68
+ protected readonly logger: ILogger;
67
69
 
68
70
  @postConstruct()
69
71
  protected init(): void {
@@ -84,7 +86,7 @@ export class PluginDebugService implements DebugService {
84
86
  const { type } = contrib;
85
87
 
86
88
  if (this.contributors.has(type)) {
87
- console.warn(`Debugger with type '${type}' already registered.`);
89
+ this.logger.warn(`Debugger with type '${type}' already registered.`);
88
90
  return Disposable.NULL;
89
91
  }
90
92
 
@@ -105,7 +107,7 @@ export class PluginDebugService implements DebugService {
105
107
  if (this.configurationProviders.has(provider.handle)) {
106
108
  const configuration = this.configurationProviders.get(provider.handle);
107
109
  if (configuration && configuration.type !== provider.type) {
108
- console.warn(`Different debug configuration provider with type '${configuration.type}' already registered.`);
110
+ this.logger.warn(`Different debug configuration provider with type '${configuration.type}' already registered.`);
109
111
  provider.handle = this.configurationProviders.size;
110
112
  }
111
113
  }
@@ -358,7 +360,7 @@ export class PluginDebugService implements DebugService {
358
360
  }
359
361
  resolved = await resolver(workspaceFolderUri, resolved);
360
362
  } catch (e) {
361
- console.error(e);
363
+ this.logger.error(e);
362
364
  }
363
365
  }
364
366
  return resolved;
@@ -14,53 +14,32 @@
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, inject, interfaces } from '@theia/core/shared/inversify';
17
18
  import { DefaultDebugSessionFactory } from '@theia/debug/lib/browser/debug-session-contribution';
18
- import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service';
19
- import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
20
- import { BreakpointManager } from '@theia/debug/lib/browser/breakpoint/breakpoint-manager';
21
- import { LabelProvider } from '@theia/core/lib/browser/label-provider';
22
- import { MessageClient } from '@theia/core/lib/common/message-service-protocol';
23
- import { OutputChannelManager } from '@theia/output/lib/browser/output-channel';
24
- import { DebugPreferences } from '@theia/debug/lib/common/debug-preferences';
25
- import { DebugConfigurationSessionOptions, TestRunReference } from '@theia/debug/lib/browser/debug-session-options';
26
- import { DebugSession } from '@theia/debug/lib/browser/debug-session';
19
+ import { DebugConfigurationSessionOptions } from '@theia/debug/lib/browser/debug-session-options';
20
+ import { DebugSession, DebugSessionData } from '@theia/debug/lib/browser/debug-session';
27
21
  import { DebugSessionConnection } from '@theia/debug/lib/browser/debug-session-connection';
28
22
  import { TerminalWidgetOptions, TerminalWidget } from '@theia/terminal/lib/browser/base/terminal-widget';
29
23
  import { TerminalOptionsExt } from '../../../common/plugin-api-rpc';
30
- import { FileService } from '@theia/filesystem/lib/browser/file-service';
31
- import { DebugContribution } from '@theia/debug/lib/browser/debug-contribution';
32
- import { ContributionProvider } from '@theia/core/lib/common/contribution-provider';
33
- import { WorkspaceService } from '@theia/workspace/lib/browser';
34
24
  import { PluginChannel } from '../../../common/connection';
35
- import { TestService } from '@theia/test/lib/browser/test-service';
36
- import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
37
- import { CommandService } from '@theia/core';
38
25
 
26
+ export const PluginTerminalOptionsExt = Symbol('PluginTerminalOptionsExt');
27
+
28
+ @injectable()
39
29
  export class PluginDebugSession extends DebugSession {
40
- constructor(
41
- override readonly id: string,
42
- override readonly options: DebugConfigurationSessionOptions,
43
- override readonly parentSession: DebugSession | undefined,
44
- testService: TestService,
45
- testRun: TestRunReference | undefined,
46
- sessionManager: DebugSessionManager,
47
- protected override readonly connection: DebugSessionConnection,
48
- protected override readonly terminalServer: TerminalService,
49
- protected override readonly editorManager: EditorManager,
50
- protected override readonly breakpoints: BreakpointManager,
51
- protected override readonly labelProvider: LabelProvider,
52
- protected override readonly messages: MessageClient,
53
- protected override readonly fileService: FileService,
54
- protected readonly terminalOptionsExt: TerminalOptionsExt | undefined,
55
- protected override readonly debugContributionProvider: ContributionProvider<DebugContribution>,
56
- protected override readonly workspaceService: WorkspaceService,
57
- debugPreferences: DebugPreferences,
58
- protected override readonly commandService: CommandService) {
59
- super(id, options, parentSession, testService, testRun, sessionManager, connection, terminalServer, editorManager, breakpoints,
60
- labelProvider, messages, fileService, debugContributionProvider,
61
- workspaceService, debugPreferences, commandService);
30
+
31
+ static override createContainer(
32
+ parent: interfaces.Container, data: DebugSessionData, connection: DebugSessionConnection, terminalOptionsExt?: TerminalOptionsExt
33
+ ): interfaces.Container {
34
+ const child = DebugSession.createContainer(parent, data, connection);
35
+ child.rebind(DebugSession).to(PluginDebugSession);
36
+ child.bind(PluginTerminalOptionsExt).toConstantValue(terminalOptionsExt);
37
+ return child;
62
38
  }
63
39
 
40
+ @inject(PluginTerminalOptionsExt)
41
+ protected readonly terminalOptionsExt: TerminalOptionsExt | undefined;
42
+
64
43
  protected override async doCreateTerminal(terminalWidgetOptions: TerminalWidgetOptions): Promise<TerminalWidget> {
65
44
  terminalWidgetOptions = Object.assign({}, terminalWidgetOptions, this.terminalOptionsExt);
66
45
  return super.doCreateTerminal(terminalWidgetOptions);
@@ -73,49 +52,24 @@ export class PluginDebugSession extends DebugSession {
73
52
  */
74
53
  export class PluginDebugSessionFactory extends DefaultDebugSessionFactory {
75
54
  constructor(
76
- protected override readonly terminalService: TerminalService,
77
- protected override readonly editorManager: EditorManager,
78
- protected override readonly breakpoints: BreakpointManager,
79
- protected override readonly labelProvider: LabelProvider,
80
- protected override readonly messages: MessageClient,
81
- protected override readonly outputChannelManager: OutputChannelManager,
82
- protected override readonly debugPreferences: DebugPreferences,
83
55
  protected readonly connectionFactory: (sessionId: string) => Promise<PluginChannel>,
84
- protected override readonly fileService: FileService,
85
56
  protected readonly terminalOptionsExt: TerminalOptionsExt | undefined,
86
- protected override readonly debugContributionProvider: ContributionProvider<DebugContribution>,
87
- protected override readonly testService: TestService,
88
- protected override readonly workspaceService: WorkspaceService,
89
- protected override readonly commandService: CommandService,
57
+ container: interfaces.Container,
90
58
  ) {
91
- super();
59
+ super(container);
92
60
  }
93
61
 
94
- override get(manager: DebugSessionManager, sessionId: string, options: DebugConfigurationSessionOptions, parentSession?: DebugSession): DebugSession {
62
+ override createSession(sessionId: string, options: DebugConfigurationSessionOptions, parentSession?: DebugSession): DebugSession {
95
63
  const connection = new DebugSessionConnection(
96
64
  sessionId,
97
65
  this.connectionFactory,
98
66
  this.getTraceOutputChannel());
99
-
100
- return new PluginDebugSession(
101
- sessionId,
67
+ const data: DebugSessionData = {
68
+ id: sessionId,
102
69
  options,
103
70
  parentSession,
104
- this.testService,
105
- options.testRun,
106
- manager,
107
- connection,
108
- this.terminalService,
109
- this.editorManager,
110
- this.breakpoints,
111
- this.labelProvider,
112
- this.messages,
113
- this.fileService,
114
- this.terminalOptionsExt,
115
- this.debugContributionProvider,
116
- this.workspaceService,
117
- this.debugPreferences,
118
- this.commandService
119
- );
71
+ };
72
+ const child = PluginDebugSession.createContainer(this.container, data, connection, this.terminalOptionsExt);
73
+ return child.get(DebugSession);
120
74
  }
121
75
  }
@@ -39,7 +39,7 @@ import {
39
39
  IdentifiableInlineCompletions,
40
40
  HoverWithId
41
41
  } from '../../common/plugin-api-rpc';
42
- import { injectable, inject } from '@theia/core/shared/inversify';
42
+ import { injectable, inject, named } from '@theia/core/shared/inversify';
43
43
  import {
44
44
  SerializedDocumentFilter, MarkerData, Range, RelatedInformation,
45
45
  MarkerSeverity, DocumentLink, WorkspaceSymbolParams, CodeAction, CompletionDto,
@@ -86,6 +86,7 @@ import { ITextModel } from '@theia/monaco-editor-core/esm/vs/editor/common/model
86
86
  import { CodeActionTriggerKind } from '../../plugin/types-impl';
87
87
  import { IReadonlyVSDataTransfer } from '@theia/monaco-editor-core/esm/vs/base/common/dataTransfer';
88
88
  import { FileUploadService } from '@theia/filesystem/lib/common/upload/file-upload';
89
+ import { ILogger } from '@theia/core';
89
90
 
90
91
  @injectable()
91
92
  export class LanguagesMainImpl implements LanguagesMain, Disposable {
@@ -108,6 +109,9 @@ export class LanguagesMainImpl implements LanguagesMain, Disposable {
108
109
  @inject(FileUploadService)
109
110
  protected readonly fileUploadService: FileUploadService;
110
111
 
112
+ @inject(ILogger) @named('plugin-ext:LanguagesMainImpl')
113
+ protected readonly logger: ILogger;
114
+
111
115
  private readonly proxy: LanguagesExt;
112
116
  private readonly services = new Map<number, Disposable>();
113
117
  private readonly toDispose = new DisposableCollection();
@@ -958,7 +962,7 @@ export class LanguagesMainImpl implements LanguagesMain, Disposable {
958
962
  dispose: () => this.proxy.$releaseCodeActions(handle, actions.map(a => a.cacheId))
959
963
  };
960
964
  } catch (e) {
961
- console.error(e);
965
+ this.logger.error(e);
962
966
  return undefined;
963
967
  }
964
968
  }
@@ -64,7 +64,7 @@ export class McpServerDefinitionRegistryMainImpl implements McpServerDefinitionR
64
64
  try {
65
65
  const definitions = await this.$getServerDefinitions(handle);
66
66
  for (const definition of definitions) {
67
- this.mcpServerManager.removeServer(definition.label);
67
+ await this.mcpServerManager.removeServer(definition.label);
68
68
  }
69
69
  } catch (error) {
70
70
  console.error('Error getting server definitions for removal:', error);
@@ -107,7 +107,7 @@ export class McpServerDefinitionRegistryMainImpl implements McpServerDefinitionR
107
107
 
108
108
  for (const definition of definitions) {
109
109
  const mcpServerDescription = this.convertToMcpServerDescription(handle, definition);
110
- this.mcpServerManager.addOrUpdateServer(mcpServerDescription);
110
+ await this.mcpServerManager.addOrUpdateServer(mcpServerDescription);
111
111
  }
112
112
  } catch (error) {
113
113
  console.error('Error loading MCP server definitions:', error);
@@ -19,7 +19,7 @@
19
19
  *--------------------------------------------------------------------------------------------*/
20
20
 
21
21
  import * as React from '@theia/core/shared/react';
22
- import { inject, injectable, interfaces } from '@theia/core/shared/inversify';
22
+ import { inject, injectable, interfaces, named } from '@theia/core/shared/inversify';
23
23
  import { generateUuid } from '@theia/core/lib/common/uuid';
24
24
  import {
25
25
  NotebookRendererMessagingService, CellOutputWebview, NotebookRendererRegistry,
@@ -36,7 +36,7 @@ import {
36
36
  CellOutputChange, CellsChangedMessage, CellsMoved, CellsSpliced,
37
37
  ChangePreferredMimetypeMessage, FromWebviewMessage, Output, OutputChangedMessage
38
38
  } from './webview-communication';
39
- import { Disposable, DisposableCollection, Emitter, QuickPickService, nls } from '@theia/core';
39
+ import { Disposable, DisposableCollection, Emitter, ILogger, QuickPickService, nls } from '@theia/core';
40
40
  import { NotebookModel } from '@theia/notebook/lib/browser/view-model/notebook-model';
41
41
  import { NotebookOptionsService, NotebookOutputOptions } from '@theia/notebook/lib/browser/service/notebook-options';
42
42
  import { NotebookCellModel } from '@theia/notebook/lib/browser/view-model/notebook-cell-model';
@@ -224,6 +224,9 @@ export class CellOutputWebviewImpl implements CellOutputWebview, Disposable {
224
224
  @inject(QuickPickService)
225
225
  protected readonly quickPickService: QuickPickService;
226
226
 
227
+ @inject(ILogger) @named('plugin-ext:CellOutputWebviewImpl')
228
+ protected readonly logger: ILogger;
229
+
227
230
  @inject(AdditionalNotebookCellOutputCss)
228
231
  protected readonly additionalOutputCss: string;
229
232
 
@@ -341,17 +344,18 @@ export class CellOutputWebviewImpl implements CellOutputWebview, Disposable {
341
344
  }
342
345
 
343
346
  render(): React.JSX.Element {
344
- return <div className='theia-notebook-cell-output-webview' ref={async element => {
347
+ return <div className='theia-notebook-cell-output-webview' ref={element => {
348
+ this.element = element ?? undefined;
345
349
  if (element) {
346
- this.element = element;
347
- await this.webviewWidgetInitialized.promise;
348
- this.attachWebview();
350
+ this.webviewWidgetInitialized.promise
351
+ .then(() => this.attachWebview())
352
+ .catch(error => this.logger.error('Failed to attach notebook output webview.', error));
349
353
  }
350
354
  }}></div>;
351
355
  }
352
356
 
353
357
  protected attachWebview(): void {
354
- if (this.element) {
358
+ if (this.element && !this.isAttached()) {
355
359
  this.webviewWidget.processMessage(new Message('before-attach'));
356
360
  this.element.appendChild(this.webviewWidget.node);
357
361
  this.webviewWidget.processMessage(new Message('after-attach'));
@@ -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[]) => console.error(`[${clientId}][${plugin.metadata.model.id}]: ${message}`, ...args);
168
- const logWarning = (message: string, ...args: any[]) => console.warn(`[${clientId}][${plugin.metadata.model.id}]: ${message}`, ...args);
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
- console.warn(`command '${command.id}' already registered`);
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
- console.warn(`command handler '${id}' already registered`);
572
+ this.logger.warn(`command handler '${id}' already registered`);
569
573
  return Disposable.NULL;
570
574
  }
571
575
 
@@ -86,7 +86,7 @@ export class PluginWidget extends ReactWidget {
86
86
  </div>;
87
87
  }
88
88
 
89
- private renderPlugin(plugin: PluginMetadata): JSX.Element {
89
+ private renderPlugin(plugin: PluginMetadata): React.JSX.Element {
90
90
  const unversionedId = PluginIdentifiers.componentsToUnversionedId(plugin.model);
91
91
  const isRestrictedByTrust = this.pluginService.disabledByTrust.has(unversionedId);
92
92
  return <div key={plugin.model.name} className={this.createPluginClassName(plugin, isRestrictedByTrust)}>
@@ -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.menus.registerMenuAction(CommonMenus.VIEW_VIEWS, {
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: webview.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
- console.error('Failed to enable webview caching: ', e);
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
- console.error(e);
632
+ this.logger.error(e);
630
633
  }
631
634
  }
632
635