@theia/debug 1.29.0-next.4 → 1.29.0-next.44

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 (83) hide show
  1. package/lib/browser/debug-configuration-manager.d.ts +15 -7
  2. package/lib/browser/debug-configuration-manager.d.ts.map +1 -1
  3. package/lib/browser/debug-configuration-manager.js +81 -31
  4. package/lib/browser/debug-configuration-manager.js.map +1 -1
  5. package/lib/browser/debug-configuration-model.d.ts +3 -0
  6. package/lib/browser/debug-configuration-model.d.ts.map +1 -1
  7. package/lib/browser/debug-configuration-model.js +17 -10
  8. package/lib/browser/debug-configuration-model.js.map +1 -1
  9. package/lib/browser/debug-frontend-application-contribution.d.ts.map +1 -1
  10. package/lib/browser/debug-frontend-application-contribution.js +6 -3
  11. package/lib/browser/debug-frontend-application-contribution.js.map +1 -1
  12. package/lib/browser/debug-prefix-configuration.js +3 -3
  13. package/lib/browser/debug-prefix-configuration.js.map +1 -1
  14. package/lib/browser/debug-schema-updater.d.ts.map +1 -1
  15. package/lib/browser/debug-schema-updater.js +55 -4
  16. package/lib/browser/debug-schema-updater.js.map +1 -1
  17. package/lib/browser/debug-session-contribution.d.ts +2 -2
  18. package/lib/browser/debug-session-contribution.d.ts.map +1 -1
  19. package/lib/browser/debug-session-contribution.js.map +1 -1
  20. package/lib/browser/debug-session-manager.d.ts +10 -4
  21. package/lib/browser/debug-session-manager.d.ts.map +1 -1
  22. package/lib/browser/debug-session-manager.js +64 -7
  23. package/lib/browser/debug-session-manager.js.map +1 -1
  24. package/lib/browser/debug-session-options.d.ts +36 -6
  25. package/lib/browser/debug-session-options.d.ts.map +1 -1
  26. package/lib/browser/debug-session-options.js +53 -10
  27. package/lib/browser/debug-session-options.js.map +1 -1
  28. package/lib/browser/debug-session.d.ts +3 -3
  29. package/lib/browser/debug-session.d.ts.map +1 -1
  30. package/lib/browser/debug-session.js +7 -0
  31. package/lib/browser/debug-session.js.map +1 -1
  32. package/lib/browser/editor/debug-breakpoint-widget.d.ts +2 -0
  33. package/lib/browser/editor/debug-breakpoint-widget.d.ts.map +1 -1
  34. package/lib/browser/editor/debug-breakpoint-widget.js +5 -4
  35. package/lib/browser/editor/debug-breakpoint-widget.js.map +1 -1
  36. package/lib/browser/editor/debug-exception-widget.d.ts +2 -0
  37. package/lib/browser/editor/debug-exception-widget.d.ts.map +1 -1
  38. package/lib/browser/editor/debug-exception-widget.js +6 -5
  39. package/lib/browser/editor/debug-exception-widget.js.map +1 -1
  40. package/lib/browser/editor/debug-hover-widget.d.ts.map +1 -1
  41. package/lib/browser/editor/debug-hover-widget.js +35 -9
  42. package/lib/browser/editor/debug-hover-widget.js.map +1 -1
  43. package/lib/browser/view/debug-configuration-select.d.ts +4 -1
  44. package/lib/browser/view/debug-configuration-select.d.ts.map +1 -1
  45. package/lib/browser/view/debug-configuration-select.js +36 -14
  46. package/lib/browser/view/debug-configuration-select.js.map +1 -1
  47. package/lib/browser/view/debug-configuration-widget.d.ts +3 -2
  48. package/lib/browser/view/debug-configuration-widget.d.ts.map +1 -1
  49. package/lib/browser/view/debug-configuration-widget.js +5 -1
  50. package/lib/browser/view/debug-configuration-widget.js.map +1 -1
  51. package/lib/browser/view/debug-toolbar-widget.d.ts +3 -2
  52. package/lib/browser/view/debug-toolbar-widget.d.ts.map +1 -1
  53. package/lib/browser/view/debug-toolbar-widget.js +5 -1
  54. package/lib/browser/view/debug-toolbar-widget.js.map +1 -1
  55. package/lib/common/debug-compound.d.ts +15 -0
  56. package/lib/common/debug-compound.d.ts.map +1 -0
  57. package/lib/common/debug-compound.js +27 -0
  58. package/lib/common/debug-compound.js.map +1 -0
  59. package/lib/common/debug-configuration.d.ts +2 -2
  60. package/lib/common/debug-configuration.d.ts.map +1 -1
  61. package/lib/common/debug-configuration.js.map +1 -1
  62. package/lib/node/debug-adapter-factory.d.ts.map +1 -1
  63. package/lib/node/debug-adapter-factory.js +1 -2
  64. package/lib/node/debug-adapter-factory.js.map +1 -1
  65. package/package.json +14 -14
  66. package/src/browser/debug-configuration-manager.ts +92 -40
  67. package/src/browser/debug-configuration-model.ts +19 -10
  68. package/src/browser/debug-frontend-application-contribution.ts +10 -3
  69. package/src/browser/debug-prefix-configuration.ts +3 -3
  70. package/src/browser/debug-schema-updater.ts +56 -5
  71. package/src/browser/debug-session-contribution.ts +2 -2
  72. package/src/browser/debug-session-manager.ts +79 -8
  73. package/src/browser/debug-session-options.ts +72 -15
  74. package/src/browser/debug-session.tsx +8 -2
  75. package/src/browser/editor/debug-breakpoint-widget.tsx +6 -4
  76. package/src/browser/editor/debug-exception-widget.tsx +7 -5
  77. package/src/browser/editor/debug-hover-widget.ts +43 -9
  78. package/src/browser/view/debug-configuration-select.tsx +42 -20
  79. package/src/browser/view/debug-configuration-widget.tsx +7 -2
  80. package/src/browser/view/debug-toolbar-widget.tsx +7 -2
  81. package/src/common/debug-compound.ts +32 -0
  82. package/src/common/debug-configuration.ts +2 -2
  83. package/src/node/debug-adapter-factory.ts +2 -3
@@ -14,7 +14,7 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import { DisposableCollection, Emitter, Event, MessageService, ProgressService, WaitUntilEvent } from '@theia/core';
17
+ import { DisposableCollection, Emitter, Event, MessageService, nls, ProgressService, WaitUntilEvent } from '@theia/core';
18
18
  import { LabelProvider, ApplicationShell } from '@theia/core/lib/browser';
19
19
  import { ContextKey, ContextKeyService } from '@theia/core/lib/browser/context-key-service';
20
20
  import URI from '@theia/core/lib/common/uri';
@@ -29,7 +29,7 @@ import { BreakpointManager } from './breakpoint/breakpoint-manager';
29
29
  import { DebugConfigurationManager } from './debug-configuration-manager';
30
30
  import { DebugSession, DebugState } from './debug-session';
31
31
  import { DebugSessionContributionRegistry, DebugSessionFactory } from './debug-session-contribution';
32
- import { DebugSessionOptions, InternalDebugSessionOptions } from './debug-session-options';
32
+ import { DebugCompoundRoot, DebugCompoundSessionOptions, DebugConfigurationSessionOptions, DebugSessionOptions, InternalDebugSessionOptions } from './debug-session-options';
33
33
  import { DebugStackFrame } from './model/debug-stack-frame';
34
34
  import { DebugThread } from './model/debug-thread';
35
35
  import { TaskIdentifier } from '@theia/task/lib/common';
@@ -191,7 +191,19 @@ export class DebugSessionManager {
191
191
  }
192
192
  }
193
193
 
194
- async start(options: DebugSessionOptions): Promise<DebugSession | undefined> {
194
+ async start(options: DebugCompoundSessionOptions): Promise<boolean | undefined>;
195
+ async start(options: DebugConfigurationSessionOptions): Promise<DebugSession | undefined>;
196
+ async start(options: DebugSessionOptions): Promise<DebugSession | boolean | undefined>;
197
+ async start(name: string): Promise<DebugSession | boolean | undefined>;
198
+ async start(optionsOrName: DebugSessionOptions | string): Promise<DebugSession | boolean | undefined> {
199
+ if (typeof optionsOrName === 'string') {
200
+ const options = this.debugConfigurationManager.find(optionsOrName);
201
+ return !!options && this.start(options);
202
+ }
203
+ return optionsOrName.configuration ? this.startConfiguration(optionsOrName) : this.startCompound(optionsOrName);
204
+ }
205
+
206
+ protected async startConfiguration(options: DebugConfigurationSessionOptions): Promise<DebugSession | undefined> {
195
207
  return this.progressService.withProgress('Start...', 'debug', async () => {
196
208
  try {
197
209
  if (!await this.saveAll()) {
@@ -200,7 +212,7 @@ export class DebugSessionManager {
200
212
  await this.fireWillStartDebugSession();
201
213
  const resolved = await this.resolveConfiguration(options);
202
214
 
203
- if (!resolved) {
215
+ if (!resolved || !resolved.configuration) {
204
216
  // As per vscode API: https://code.visualstudio.com/api/references/vscode-api#DebugConfigurationProvider
205
217
  // "Returning the value 'undefined' prevents the debug session from starting.
206
218
  // Returning the value 'null' prevents the debug session from starting and opens the
@@ -236,13 +248,70 @@ export class DebugSessionManager {
236
248
  });
237
249
  }
238
250
 
251
+ protected async startCompound(options: DebugCompoundSessionOptions): Promise<boolean | undefined> {
252
+ let configurations: DebugConfigurationSessionOptions[] = [];
253
+ try {
254
+ configurations = this.getCompoundConfigurations(options);
255
+ } catch (error) {
256
+ this.messageService.error(error.message);
257
+ return;
258
+ }
259
+
260
+ if (options.compound.preLaunchTask) {
261
+ const taskRun = await this.runTask(options.workspaceFolderUri, options.compound.preLaunchTask, true);
262
+ if (!taskRun) {
263
+ return undefined;
264
+ }
265
+ }
266
+
267
+ // Compound launch is a success only if each configuration launched successfully
268
+ const values = await Promise.all(configurations.map(configuration => this.startConfiguration(configuration)));
269
+ const result = values.every(success => !!success);
270
+ return result;
271
+ }
272
+
273
+ protected getCompoundConfigurations(options: DebugCompoundSessionOptions): DebugConfigurationSessionOptions[] {
274
+ const compound = options.compound;
275
+ if (!compound.configurations) {
276
+ throw new Error(nls.localizeByDefault('Compound must have "configurations" attribute set in order to start multiple configurations.'));
277
+ }
278
+
279
+ const compoundRoot = compound.stopAll ? new DebugCompoundRoot() : undefined;
280
+ const configurations: DebugConfigurationSessionOptions[] = [];
281
+ for (const configData of compound.configurations) {
282
+ const name = typeof configData === 'string' ? configData : configData.name;
283
+ if (name === compound.name) {
284
+ throw new Error(nls.localize('theia/debug/compound-cycle', "Launch configuration '{0}' contains a cycle with itself", name));
285
+ }
286
+
287
+ const workspaceFolderUri = typeof configData === 'string' ? options.workspaceFolderUri : configData.folder;
288
+ const matchingOptions = [...this.debugConfigurationManager.all]
289
+ .filter(option => option.name === name && !!option.configuration && option.workspaceFolderUri === workspaceFolderUri);
290
+ if (matchingOptions.length === 1) {
291
+ const match = matchingOptions[0];
292
+ if (DebugSessionOptions.isConfiguration(match)) {
293
+ configurations.push({ ...match, compoundRoot, configuration: { ...match.configuration, noDebug: options.noDebug } });
294
+ } else {
295
+ throw new Error(nls.localizeByDefault("Could not find launch configuration '{0}' in the workspace.", name));
296
+ }
297
+ } else {
298
+ throw new Error(matchingOptions.length === 0
299
+ ? workspaceFolderUri
300
+ ? nls.localizeByDefault("Can not find folder with name '{0}' for configuration '{1}' in compound '{2}'.", workspaceFolderUri, name, compound.name)
301
+ : nls.localizeByDefault("Could not find launch configuration '{0}' in the workspace.", name)
302
+ : nls.localizeByDefault("There are multiple launch configurations '{0}' in the workspace. Use folder name to qualify the configuration.", name));
303
+ }
304
+ }
305
+ return configurations;
306
+ }
307
+
239
308
  protected async fireWillStartDebugSession(): Promise<void> {
240
309
  await WaitUntilEvent.fire(this.onWillStartDebugSessionEmitter, {});
241
310
  }
242
311
 
243
312
  protected configurationIds = new Map<string, number>();
244
313
  protected async resolveConfiguration(
245
- options: Readonly<DebugSessionOptions>
314
+ options: Readonly<DebugConfigurationSessionOptions>
246
315
  ): Promise<InternalDebugSessionOptions | undefined | null> {
247
316
  if (InternalDebugSessionOptions.is(options)) {
248
317
  return options;
@@ -275,10 +344,12 @@ export class DebugSessionManager {
275
344
  const key = configuration.name + workspaceFolderUri;
276
345
  const id = this.configurationIds.has(key) ? this.configurationIds.get(key)! + 1 : 0;
277
346
  this.configurationIds.set(key, id);
347
+
278
348
  return {
279
349
  id,
280
- configuration,
281
- workspaceFolderUri
350
+ ...options,
351
+ name: configuration.name,
352
+ configuration
282
353
  };
283
354
  }
284
355
 
@@ -301,7 +372,7 @@ export class DebugSessionManager {
301
372
  return this.debug.resolveDebugConfigurationWithSubstitutedVariables(configuration, workspaceFolderUri);
302
373
  }
303
374
 
304
- protected async doStart(sessionId: string, options: DebugSessionOptions): Promise<DebugSession> {
375
+ protected async doStart(sessionId: string, options: DebugConfigurationSessionOptions): Promise<DebugSession> {
305
376
  const parentSession = options.configuration.parentSession && this._sessions.get(options.configuration.parentSession.id);
306
377
  const contrib = this.sessionContributionRegistry.get(options.configuration.type);
307
378
  const sessionFactory = contrib ? contrib.debugSessionFactory() : this.debugSessionFactory;
@@ -14,44 +14,101 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
+ import { Emitter } from '@theia/core';
17
18
  import { DebugConfiguration } from '../common/debug-common';
19
+ import { DebugCompound } from '../common/debug-compound';
20
+
21
+ export class DebugCompoundRoot {
22
+ private stopped = false;
23
+ private stopEmitter = new Emitter<void>();
24
+ onDidSessionStop = this.stopEmitter.event;
25
+
26
+ stopSession(): void {
27
+ if (!this.stopped) { // avoid sending extraneous terminate events
28
+ this.stopped = true;
29
+ this.stopEmitter.fire();
30
+ }
31
+ }
32
+ }
18
33
 
19
34
  export interface DebugSessionOptionsBase {
20
35
  workspaceFolderUri?: string,
21
- providerType?: string // Applicable to dynamic configurations
22
36
  }
23
37
 
24
- export interface DebugSessionOptions extends DebugSessionOptionsBase {
25
- configuration: DebugConfiguration
38
+ export interface DebugConfigurationSessionOptions extends DebugSessionOptionsBase {
39
+ name: string; // derived from the configuration
40
+ configuration: DebugConfiguration;
41
+ compound?: never;
42
+ compoundRoot?: DebugCompoundRoot;
43
+ providerType?: string // Applicable to dynamic configurations
26
44
  }
27
45
 
28
- export interface DebugSessionOptionsData extends DebugSessionOptionsBase, DebugConfiguration {
46
+ export type DynamicDebugConfigurationSessionOptions = DebugConfigurationSessionOptions & { providerType: string };
47
+
48
+ export interface DebugCompoundSessionOptions extends DebugSessionOptionsBase {
49
+ name: string; // derived from the compound
50
+ configuration?: never;
51
+ compound: DebugCompound;
52
+ noDebug?: boolean,
29
53
  }
30
54
 
31
- export interface InternalDebugSessionOptions extends DebugSessionOptions {
32
- id: number
55
+ export type DebugSessionOptions = DebugConfigurationSessionOptions | DebugCompoundSessionOptions;
56
+
57
+ export namespace DebugSessionOptions {
58
+ export function isConfiguration(options?: DebugSessionOptions): options is DebugConfigurationSessionOptions {
59
+ return !!options && 'configuration' in options && !!options.configuration;
60
+ }
61
+
62
+ export function isDynamic(options?: DebugSessionOptions): options is DynamicDebugConfigurationSessionOptions {
63
+ return isConfiguration(options) && 'providerType' in options && !!options.providerType;
64
+ }
65
+
66
+ export function isCompound(options?: DebugSessionOptions): options is DebugCompoundSessionOptions {
67
+ return !!options && 'compound' in options && !!options.compound;
68
+ }
33
69
  }
70
+
71
+ /**
72
+ * Flat and partial version of a debug session options usable to find the options later in the manager.
73
+ * @deprecated Not needed anymore, the recommended way is to serialize/deserialize the options directly using `JSON.stringify` and `JSON.parse`.
74
+ */
75
+ export type DebugSessionOptionsData = DebugSessionOptionsBase & (DebugConfiguration | DebugCompound);
76
+
77
+ export type InternalDebugSessionOptions = DebugSessionOptions & { id: number };
78
+
34
79
  export namespace InternalDebugSessionOptions {
35
80
 
36
81
  const SEPARATOR = '__CONF__';
82
+ const SEPARATOR_CONFIGS = '__COMP__';
37
83
 
38
84
  export function is(options: DebugSessionOptions): options is InternalDebugSessionOptions {
39
85
  return 'id' in options;
40
86
  }
41
87
 
42
- export function toValue(debugSessionOptions: DebugSessionOptions): string {
43
- return debugSessionOptions.configuration.name + SEPARATOR +
44
- debugSessionOptions.configuration.type + SEPARATOR +
45
- debugSessionOptions.configuration.request + SEPARATOR +
46
- debugSessionOptions.workspaceFolderUri + SEPARATOR +
47
- debugSessionOptions.providerType;
88
+ /** @deprecated Please use `JSON.stringify` to serialize the options. */
89
+ export function toValue(options: DebugSessionOptions): string {
90
+ if (DebugSessionOptions.isCompound(options)) {
91
+ return options.compound.name + SEPARATOR +
92
+ options.workspaceFolderUri + SEPARATOR +
93
+ options.compound?.configurations.join(SEPARATOR_CONFIGS);
94
+ }
95
+ return options.configuration.name + SEPARATOR +
96
+ options.configuration.type + SEPARATOR +
97
+ options.configuration.request + SEPARATOR +
98
+ options.workspaceFolderUri + SEPARATOR +
99
+ options.providerType;
48
100
  }
49
101
 
102
+ /** @deprecated Please use `JSON.parse` to restore previously serialized debug session options. */
103
+ // eslint-disable-next-line deprecation/deprecation
50
104
  export function parseValue(value: string): DebugSessionOptionsData {
51
105
  const split = value.split(SEPARATOR);
52
- if (split.length !== 5) {
53
- throw new Error('Unexpected argument, the argument is expected to have been generated by the \'toValue\' function');
106
+ if (split.length === 5) {
107
+ return { name: split[0], type: split[1], request: split[2], workspaceFolderUri: split[3], providerType: split[4] };
108
+ }
109
+ if (split.length === 3) {
110
+ return { name: split[0], workspaceFolderUri: split[1], configurations: split[2].split(SEPARATOR_CONFIGS) };
54
111
  }
55
- return {name: split[0], type: split[1], request: split[2], workspaceFolderUri: split[3], providerType: split[4]};
112
+ throw new Error('Unexpected argument, the argument is expected to have been generated by the \'toValue\' function');
56
113
  }
57
114
  }
@@ -33,7 +33,7 @@ import { DebugSourceBreakpoint } from './model/debug-source-breakpoint';
33
33
  import debounce = require('p-debounce');
34
34
  import URI from '@theia/core/lib/common/uri';
35
35
  import { BreakpointManager } from './breakpoint/breakpoint-manager';
36
- import { DebugSessionOptions, InternalDebugSessionOptions } from './debug-session-options';
36
+ import { DebugConfigurationSessionOptions, InternalDebugSessionOptions } from './debug-session-options';
37
37
  import { DebugConfiguration, DebugConsoleMode } from '../common/debug-common';
38
38
  import { SourceBreakpoint, ExceptionBreakpoint } from './breakpoint/breakpoint-marker';
39
39
  import { TerminalWidgetOptions, TerminalWidget } from '@theia/terminal/lib/browser/base/terminal-widget';
@@ -76,7 +76,7 @@ export class DebugSession implements CompositeTreeElement {
76
76
 
77
77
  constructor(
78
78
  readonly id: string,
79
- readonly options: DebugSessionOptions,
79
+ readonly options: DebugConfigurationSessionOptions,
80
80
  readonly parentSession: DebugSession | undefined,
81
81
  protected readonly connection: DebugSessionConnection,
82
82
  protected readonly terminalServer: TerminalService,
@@ -117,6 +117,9 @@ export class DebugSession implements CompositeTreeElement {
117
117
  this.connection.on('capabilities', event => this.updateCapabilities(event.body.capabilities)),
118
118
  this.breakpoints.onDidChangeMarkers(uri => this.updateBreakpoints({ uri, sourceModified: true }))
119
119
  ]);
120
+ if (this.options.compoundRoot) {
121
+ this.toDispose.push(this.options.compoundRoot.onDidSessionStop(() => this.stop(false, () => { })));
122
+ }
120
123
  }
121
124
 
122
125
  get onDispose(): Event<void> {
@@ -352,6 +355,9 @@ export class DebugSession implements CompositeTreeElement {
352
355
  console.error('Error on disconnect', e);
353
356
  }
354
357
  }
358
+ if (!isRestart) {
359
+ this.options.compoundRoot?.stopSession();
360
+ }
355
361
  callback();
356
362
  }
357
363
  }
@@ -15,7 +15,7 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import * as React from '@theia/core/shared/react';
18
- import * as ReactDOM from '@theia/core/shared/react-dom';
18
+ import { createRoot, Root } from '@theia/core/shared/react-dom/client';
19
19
  import { DebugProtocol } from 'vscode-debugprotocol';
20
20
  import { injectable, postConstruct, inject } from '@theia/core/shared/inversify';
21
21
  import { Disposable, DisposableCollection, nls } from '@theia/core';
@@ -55,6 +55,7 @@ export class DebugBreakpointWidget implements Disposable {
55
55
  protected readonly editorProvider: MonacoEditorProvider;
56
56
 
57
57
  protected selectNode: HTMLDivElement;
58
+ protected selectNodeRoot: Root;
58
59
 
59
60
  protected zone: MonacoEditorZoneWidget;
60
61
 
@@ -99,6 +100,8 @@ export class DebugBreakpointWidget implements Disposable {
99
100
  const selectNode = this.selectNode = document.createElement('div');
100
101
  selectNode.classList.add('theia-debug-breakpoint-select');
101
102
  this.zone.containerNode.appendChild(selectNode);
103
+ this.selectNodeRoot = createRoot(this.selectNode);
104
+ this.toDispose.push(Disposable.create(() => this.selectNodeRoot.unmount()));
102
105
 
103
106
  const inputNode = document.createElement('div');
104
107
  inputNode.classList.add('theia-debug-breakpoint-input');
@@ -148,7 +151,6 @@ export class DebugBreakpointWidget implements Disposable {
148
151
  this.zone.layout(heightInLines);
149
152
  this.updatePlaceholder();
150
153
  }));
151
- this.toDispose.push(Disposable.create(() => ReactDOM.unmountComponentAtNode(selectNode)));
152
154
  }
153
155
 
154
156
  dispose(): void {
@@ -213,14 +215,14 @@ export class DebugBreakpointWidget implements Disposable {
213
215
  if (this._input) {
214
216
  this._input.getControl().setValue(this._values[this.context] || '');
215
217
  }
216
- ReactDOM.render(<SelectComponent
218
+ this.selectNodeRoot.render(<SelectComponent
217
219
  defaultValue={this.context} onChange={this.updateInput}
218
220
  options={[
219
221
  { value: 'condition', label: nls.localizeByDefault('Expression') },
220
222
  { value: 'hitCondition', label: nls.localizeByDefault('Hit Count') },
221
223
  { value: 'logMessage', label: nls.localizeByDefault('Log Message') },
222
224
  ]}
223
- />, this.selectNode);
225
+ />);
224
226
  }
225
227
 
226
228
  protected readonly updateInput = (option: SelectOption) => {
@@ -15,7 +15,7 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import * as React from '@theia/core/shared/react';
18
- import * as ReactDOM from '@theia/core/shared/react-dom';
18
+ import { createRoot, Root } from '@theia/core/shared/react-dom/client';
19
19
  import * as monaco from '@theia/monaco-editor-core';
20
20
  import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
21
21
  import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
@@ -54,6 +54,7 @@ export class DebugExceptionWidget implements Disposable {
54
54
  readonly editor: DebugEditor;
55
55
 
56
56
  protected zone: MonacoEditorZoneWidget;
57
+ protected containerNodeRoot: Root;
57
58
 
58
59
  protected readonly toDispose = new DisposableCollection();
59
60
 
@@ -61,7 +62,8 @@ export class DebugExceptionWidget implements Disposable {
61
62
  protected async init(): Promise<void> {
62
63
  this.toDispose.push(this.zone = new DebugExceptionMonacoEditorZoneWidget(this.editor.getControl()));
63
64
  this.zone.containerNode.classList.add('theia-debug-exception-widget');
64
- this.toDispose.push(Disposable.create(() => ReactDOM.unmountComponentAtNode(this.zone.containerNode)));
65
+ this.containerNodeRoot = createRoot(this.zone.containerNode);
66
+ this.toDispose.push(Disposable.create(() => this.containerNodeRoot.unmount()));
65
67
  this.toDispose.push(this.editor.getControl().onDidLayoutChange(() => this.layout()));
66
68
  }
67
69
 
@@ -94,14 +96,14 @@ export class DebugExceptionWidget implements Disposable {
94
96
  const exceptionTitle = info.id ?
95
97
  nls.localizeByDefault('Exception has occurred: {0}', info.id) :
96
98
  nls.localizeByDefault('Exception has occurred.');
97
- ReactDOM.render(<React.Fragment>
98
- <div className='title'>
99
+ this.containerNodeRoot.render(<React.Fragment>
100
+ <div className='title' ref={cb}>
99
101
  {exceptionTitle}
100
102
  <span id="exception-close" className={codicon('close', true)} onClick={() => this.hide()} title={nls.localizeByDefault('Close')}></span>
101
103
  </div>
102
104
  {info.description && <div className='description'>{info.description}</div>}
103
105
  {stackTrace && <div className='stack-trace'>{stackTrace}</div>}
104
- </React.Fragment>, this.zone.containerNode, cb);
106
+ </React.Fragment>);
105
107
  }
106
108
 
107
109
  protected layout(): void {
@@ -28,6 +28,11 @@ import { DebugExpressionProvider } from './debug-expression-provider';
28
28
  import { DebugHoverSource } from './debug-hover-source';
29
29
  import { DebugVariable } from '../console/debug-console-items';
30
30
  import * as monaco from '@theia/monaco-editor-core';
31
+ import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
32
+ import { ILanguageFeaturesService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languageFeatures';
33
+ import { CancellationTokenSource } from '@theia/monaco-editor-core/esm/vs/base/common/cancellation';
34
+ import { Position } from '@theia/monaco-editor-core/esm/vs/editor/common/core/position';
35
+ import { ArrayUtils } from '@theia/core';
31
36
 
32
37
  export interface ShowDebugHoverOptions {
33
38
  selection: monaco.Range
@@ -146,6 +151,8 @@ export class DebugHoverWidget extends SourceTreeWidget implements monaco.editor.
146
151
  }
147
152
 
148
153
  protected async doShow(options: ShowDebugHoverOptions | undefined = this.options): Promise<void> {
154
+ const cancellationSource = new CancellationTokenSource();
155
+
149
156
  if (!this.isEditorFrame()) {
150
157
  this.hide();
151
158
  return;
@@ -162,7 +169,33 @@ export class DebugHoverWidget extends SourceTreeWidget implements monaco.editor.
162
169
  }
163
170
 
164
171
  this.options = options;
165
- const matchingExpression = this.expressionProvider.get(this.editor.getControl().getModel()!, options.selection);
172
+ let matchingExpression: string | undefined;
173
+
174
+ const pluginExpressionProvider = StandaloneServices.get(ILanguageFeaturesService).evaluatableExpressionProvider;
175
+ const textEditorModel = this.editor.document.textEditorModel;
176
+
177
+ if (pluginExpressionProvider && pluginExpressionProvider.has(textEditorModel)) {
178
+ const registeredProviders = pluginExpressionProvider.ordered(textEditorModel);
179
+ const position = new Position(this.options!.selection.startLineNumber, this.options!.selection.startColumn);
180
+
181
+ const promises = registeredProviders.map(support =>
182
+ Promise.resolve(support.provideEvaluatableExpression(textEditorModel, position, cancellationSource.token))
183
+ );
184
+
185
+ const results = await Promise.all(promises).then(ArrayUtils.coalesce);
186
+ if (results.length > 0) {
187
+ matchingExpression = results[0].expression;
188
+ const range = results[0].range;
189
+
190
+ if (!matchingExpression) {
191
+ const lineContent = textEditorModel.getLineContent(position.lineNumber);
192
+ matchingExpression = lineContent.substring(range.startColumn - 1, range.endColumn - 1);
193
+ }
194
+ }
195
+ } else { // use fallback if no provider was registered
196
+ matchingExpression = this.expressionProvider.get(this.editor.getControl().getModel()!, options.selection);
197
+ }
198
+
166
199
  if (!matchingExpression) {
167
200
  this.hide();
168
201
  return;
@@ -217,14 +250,15 @@ export class DebugHoverWidget extends SourceTreeWidget implements monaco.editor.
217
250
  return undefined!;
218
251
  }
219
252
  const position = this.options && this.options.selection.getStartPosition();
220
- const word = position && this.editor.getControl().getModel()!.getWordAtPosition(position);
221
- return position && word ? {
222
- position: new monaco.Position(position.lineNumber, word.startColumn),
223
- preference: [
224
- monaco.editor.ContentWidgetPositionPreference.ABOVE,
225
- monaco.editor.ContentWidgetPositionPreference.BELOW
226
- ]
227
- } : undefined!;
253
+ return position
254
+ ? {
255
+ position: new monaco.Position(position.lineNumber, position.column),
256
+ preference: [
257
+ monaco.editor.ContentWidgetPositionPreference.ABOVE,
258
+ monaco.editor.ContentWidgetPositionPreference.BELOW,
259
+ ],
260
+ }
261
+ : undefined!;
228
262
  }
229
263
 
230
264
  protected override onUpdateRequest(msg: Message): void {
@@ -17,7 +17,7 @@
17
17
  import URI from '@theia/core/lib/common/uri';
18
18
  import * as React from '@theia/core/shared/react';
19
19
  import { DebugConfigurationManager } from '../debug-configuration-manager';
20
- import { DebugSessionOptions, InternalDebugSessionOptions } from '../debug-session-options';
20
+ import { DebugSessionOptions } from '../debug-session-options';
21
21
  import { SelectComponent, SelectOption } from '@theia/core/lib/browser/widgets/select-component';
22
22
  import { QuickInputService } from '@theia/core/lib/browser';
23
23
  import { nls } from '@theia/core/lib/common/nls';
@@ -40,6 +40,7 @@ export class DebugConfigurationSelect extends React.Component<DebugConfiguration
40
40
  protected static readonly PICK = '__PICK__';
41
41
  protected static readonly NO_CONFIGURATION = '__NO_CONF__';
42
42
  protected static readonly ADD_CONFIGURATION = '__ADD_CONF__';
43
+ protected static readonly CONFIG_MARKER = '__CONFIG__';
43
44
 
44
45
  private readonly selectRef = React.createRef<SelectComponent>();
45
46
  private manager: DebugConfigurationManager;
@@ -65,7 +66,7 @@ export class DebugConfigurationSelect extends React.Component<DebugConfiguration
65
66
  override render(): React.ReactNode {
66
67
  return <SelectComponent
67
68
  options={this.renderOptions()}
68
- defaultValue={this.currentValue}
69
+ defaultValue={this.state.currentValue}
69
70
  onChange={option => this.setCurrentConfiguration(option)}
70
71
  onFocus={() => this.refreshDebugConfigurations()}
71
72
  onBlur={() => this.refreshDebugConfigurations()}
@@ -75,7 +76,26 @@ export class DebugConfigurationSelect extends React.Component<DebugConfiguration
75
76
 
76
77
  protected get currentValue(): string {
77
78
  const { current } = this.manager;
78
- return current ? InternalDebugSessionOptions.toValue(current) : DebugConfigurationSelect.NO_CONFIGURATION;
79
+ const matchingOption = this.getCurrentOption(current);
80
+ return matchingOption ? matchingOption.value! : current ? JSON.stringify(current) : DebugConfigurationSelect.NO_CONFIGURATION;
81
+ }
82
+
83
+ protected getCurrentOption(current: DebugSessionOptions | undefined): SelectOption | undefined {
84
+ if (!current || !this.selectRef.current) {
85
+ return;
86
+ }
87
+ const matchingOption = this.selectRef.current!.options.find(option =>
88
+ option.userData === DebugConfigurationSelect.CONFIG_MARKER
89
+ && this.matchesOption(JSON.parse(option.value!), current)
90
+ );
91
+ return matchingOption;
92
+ }
93
+
94
+ protected matchesOption(sessionOption: DebugSessionOptions, current: DebugSessionOptions): boolean {
95
+ const matchesNameAndWorkspace = sessionOption.name === current.name && sessionOption.workspaceFolderUri === current.workspaceFolderUri;
96
+ return DebugSessionOptions.isConfiguration(sessionOption) && DebugSessionOptions.isConfiguration(current)
97
+ ? matchesNameAndWorkspace && sessionOption.providerType === current.providerType
98
+ : matchesNameAndWorkspace;
79
99
  }
80
100
 
81
101
  protected readonly setCurrentConfiguration = (option: SelectOption) => {
@@ -88,12 +108,9 @@ export class DebugConfigurationSelect extends React.Component<DebugConfiguration
88
108
  const providerType = this.parsePickValue(value);
89
109
  this.selectDynamicConfigFromQuickPick(providerType);
90
110
  } else {
91
- const { name, type, request, workspaceFolderUri, providerType } = InternalDebugSessionOptions.parseValue(value);
92
- this.manager.current = this.manager.find(
93
- { name, type, request },
94
- workspaceFolderUri,
95
- providerType === 'undefined' ? undefined : providerType
96
- );
111
+ const data = JSON.parse(value) as DebugSessionOptions;
112
+ this.manager.current = data;
113
+ this.refreshDebugConfigurations();
97
114
  }
98
115
  };
99
116
 
@@ -151,13 +168,15 @@ export class DebugConfigurationSelect extends React.Component<DebugConfiguration
151
168
  protected refreshDebugConfigurations = async () => {
152
169
  const configsPerType = await this.manager.provideDynamicDebugConfigurations();
153
170
  const providerTypes = [];
154
- for (const [ type, configurations ] of Object.entries(configsPerType)) {
171
+ for (const [type, configurations] of Object.entries(configsPerType)) {
155
172
  if (configurations.length > 0) {
156
173
  providerTypes.push(type);
157
174
  }
158
175
  }
159
- this.selectRef.current!.value = this.currentValue;
160
- this.setState({ providerTypes, currentValue: this.currentValue });
176
+
177
+ const value = this.currentValue;
178
+ this.selectRef.current!.value = value;
179
+ this.setState({ providerTypes, currentValue: value });
161
180
  };
162
181
 
163
182
  protected renderOptions(): SelectOption[] {
@@ -165,10 +184,11 @@ export class DebugConfigurationSelect extends React.Component<DebugConfiguration
165
184
 
166
185
  // Add non dynamic debug configurations
167
186
  for (const config of this.manager.all) {
168
- const value = InternalDebugSessionOptions.toValue(config);
187
+ const value = JSON.stringify(config);
169
188
  options.push({
170
189
  value,
171
- label: this.toName(config, this.props.isMultiRoot)
190
+ label: this.toName(config, this.props.isMultiRoot),
191
+ userData: DebugConfigurationSelect.CONFIG_MARKER
172
192
  });
173
193
  }
174
194
 
@@ -181,10 +201,11 @@ export class DebugConfigurationSelect extends React.Component<DebugConfiguration
181
201
  });
182
202
  }
183
203
  for (const dynamicOption of recentDynamicOptions) {
184
- const value = InternalDebugSessionOptions.toValue(dynamicOption);
204
+ const value = JSON.stringify(dynamicOption);
185
205
  options.push({
186
206
  value,
187
- label: this.toName(dynamicOption, this.props.isMultiRoot) + ' (' + dynamicOption.providerType + ')'
207
+ label: this.toName(dynamicOption, this.props.isMultiRoot) + ' (' + dynamicOption.providerType + ')',
208
+ userData: DebugConfigurationSelect.CONFIG_MARKER
188
209
  });
189
210
  }
190
211
  }
@@ -225,10 +246,11 @@ export class DebugConfigurationSelect extends React.Component<DebugConfiguration
225
246
  return options;
226
247
  }
227
248
 
228
- protected toName({ configuration, workspaceFolderUri }: DebugSessionOptions, multiRoot: boolean): string {
229
- if (!workspaceFolderUri || !multiRoot) {
230
- return configuration.name;
249
+ protected toName(options: DebugSessionOptions, multiRoot: boolean): string {
250
+ const name = options.configuration?.name ?? options.name;
251
+ if (!options.workspaceFolderUri || !multiRoot) {
252
+ return name;
231
253
  }
232
- return `${configuration.name} (${new URI(workspaceFolderUri).path.base})`;
254
+ return `${name} (${new URI(options.workspaceFolderUri).path.base})`;
233
255
  }
234
256
  }
@@ -15,7 +15,7 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { ReactWidget, QuickInputService } from '@theia/core/lib/browser';
18
- import { CommandRegistry, Disposable, MessageService } from '@theia/core/lib/common';
18
+ import { CommandRegistry, Disposable, DisposableCollection, MessageService } from '@theia/core/lib/common';
19
19
  import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
20
20
  import * as React from '@theia/core/shared/react';
21
21
  import { WorkspaceService } from '@theia/workspace/lib/browser';
@@ -55,6 +55,8 @@ export class DebugConfigurationWidget extends ReactWidget {
55
55
  @inject(MessageService)
56
56
  protected readonly messageService: MessageService;
57
57
 
58
+ protected readonly onRender = new DisposableCollection();
59
+
58
60
  @postConstruct()
59
61
  protected init(): void {
60
62
  this.addClass('debug-toolbar');
@@ -80,7 +82,10 @@ export class DebugConfigurationWidget extends ReactWidget {
80
82
  }
81
83
 
82
84
  protected stepRef: DebugAction | undefined;
83
- protected setStepRef = (stepRef: DebugAction | null) => this.stepRef = stepRef || undefined;
85
+ protected setStepRef = (stepRef: DebugAction | null) => {
86
+ this.stepRef = stepRef || undefined;
87
+ this.onRender.dispose();
88
+ };
84
89
 
85
90
  render(): React.ReactNode {
86
91
  return <React.Fragment>