@theia/debug 1.67.0-next.59 → 1.67.0

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 (145) hide show
  1. package/lib/browser/breakpoint/breakpoint-manager.js +1 -1
  2. package/lib/browser/breakpoint/breakpoint-manager.js.map +1 -1
  3. package/lib/browser/console/debug-console-items.d.ts +13 -3
  4. package/lib/browser/console/debug-console-items.d.ts.map +1 -1
  5. package/lib/browser/console/debug-console-items.js +81 -20
  6. package/lib/browser/console/debug-console-items.js.map +1 -1
  7. package/lib/browser/console/debug-console-session.d.ts.map +1 -1
  8. package/lib/browser/console/debug-console-session.js +1 -0
  9. package/lib/browser/console/debug-console-session.js.map +1 -1
  10. package/lib/browser/debug-commands.d.ts +153 -0
  11. package/lib/browser/debug-commands.d.ts.map +1 -0
  12. package/lib/browser/debug-commands.js +399 -0
  13. package/lib/browser/debug-commands.js.map +1 -0
  14. package/lib/browser/debug-frontend-application-contribution.d.ts +1 -150
  15. package/lib/browser/debug-frontend-application-contribution.d.ts.map +1 -1
  16. package/lib/browser/debug-frontend-application-contribution.js +155 -532
  17. package/lib/browser/debug-frontend-application-contribution.js.map +1 -1
  18. package/lib/browser/debug-prefix-configuration.js +3 -3
  19. package/lib/browser/debug-prefix-configuration.js.map +1 -1
  20. package/lib/browser/debug-session-contribution.d.ts +2 -1
  21. package/lib/browser/debug-session-contribution.d.ts.map +1 -1
  22. package/lib/browser/debug-session-contribution.js +5 -1
  23. package/lib/browser/debug-session-contribution.js.map +1 -1
  24. package/lib/browser/debug-session-manager.d.ts +11 -3
  25. package/lib/browser/debug-session-manager.d.ts.map +1 -1
  26. package/lib/browser/debug-session-manager.js +49 -8
  27. package/lib/browser/debug-session-manager.js.map +1 -1
  28. package/lib/browser/debug-session.d.ts +9 -3
  29. package/lib/browser/debug-session.d.ts.map +1 -1
  30. package/lib/browser/debug-session.js +13 -3
  31. package/lib/browser/debug-session.js.map +1 -1
  32. package/lib/browser/editor/debug-hover-source.d.ts +1 -0
  33. package/lib/browser/editor/debug-hover-source.d.ts.map +1 -1
  34. package/lib/browser/editor/debug-hover-source.js +9 -0
  35. package/lib/browser/editor/debug-hover-source.js.map +1 -1
  36. package/lib/browser/editor/debug-inline-value-decorator.js +1 -1
  37. package/lib/browser/editor/debug-inline-value-decorator.js.map +1 -1
  38. package/lib/browser/model/debug-breakpoint.d.ts +4 -2
  39. package/lib/browser/model/debug-breakpoint.d.ts.map +1 -1
  40. package/lib/browser/model/debug-breakpoint.js +10 -2
  41. package/lib/browser/model/debug-breakpoint.js.map +1 -1
  42. package/lib/browser/model/debug-data-breakpoint.d.ts +2 -0
  43. package/lib/browser/model/debug-data-breakpoint.d.ts.map +1 -1
  44. package/lib/browser/model/debug-data-breakpoint.js +16 -5
  45. package/lib/browser/model/debug-data-breakpoint.js.map +1 -1
  46. package/lib/browser/model/debug-function-breakpoint.d.ts +3 -0
  47. package/lib/browser/model/debug-function-breakpoint.d.ts.map +1 -1
  48. package/lib/browser/model/debug-function-breakpoint.js +17 -1
  49. package/lib/browser/model/debug-function-breakpoint.js.map +1 -1
  50. package/lib/browser/model/debug-instruction-breakpoint.d.ts +2 -0
  51. package/lib/browser/model/debug-instruction-breakpoint.d.ts.map +1 -1
  52. package/lib/browser/model/debug-instruction-breakpoint.js +13 -1
  53. package/lib/browser/model/debug-instruction-breakpoint.js.map +1 -1
  54. package/lib/browser/model/debug-source-breakpoint.d.ts +6 -1
  55. package/lib/browser/model/debug-source-breakpoint.d.ts.map +1 -1
  56. package/lib/browser/model/debug-source-breakpoint.js +17 -1
  57. package/lib/browser/model/debug-source-breakpoint.js.map +1 -1
  58. package/lib/browser/model/debug-stack-frame.d.ts +2 -2
  59. package/lib/browser/model/debug-stack-frame.d.ts.map +1 -1
  60. package/lib/browser/model/debug-stack-frame.js +13 -5
  61. package/lib/browser/model/debug-stack-frame.js.map +1 -1
  62. package/lib/browser/model/debug-thread.d.ts +2 -2
  63. package/lib/browser/model/debug-thread.d.ts.map +1 -1
  64. package/lib/browser/model/debug-thread.js +13 -9
  65. package/lib/browser/model/debug-thread.js.map +1 -1
  66. package/lib/browser/view/debug-action.d.ts +1 -0
  67. package/lib/browser/view/debug-action.d.ts.map +1 -1
  68. package/lib/browser/view/debug-action.js +2 -2
  69. package/lib/browser/view/debug-action.js.map +1 -1
  70. package/lib/browser/view/debug-breakpoints-source.d.ts +2 -0
  71. package/lib/browser/view/debug-breakpoints-source.d.ts.map +1 -1
  72. package/lib/browser/view/debug-breakpoints-source.js +6 -1
  73. package/lib/browser/view/debug-breakpoints-source.js.map +1 -1
  74. package/lib/browser/view/debug-configuration-widget.js +2 -2
  75. package/lib/browser/view/debug-configuration-widget.js.map +1 -1
  76. package/lib/browser/view/debug-exception-breakpoint.d.ts +9 -2
  77. package/lib/browser/view/debug-exception-breakpoint.d.ts.map +1 -1
  78. package/lib/browser/view/debug-exception-breakpoint.js +26 -5
  79. package/lib/browser/view/debug-exception-breakpoint.js.map +1 -1
  80. package/lib/browser/view/debug-session-widget.d.ts +0 -1
  81. package/lib/browser/view/debug-session-widget.d.ts.map +1 -1
  82. package/lib/browser/view/debug-session-widget.js +0 -4
  83. package/lib/browser/view/debug-session-widget.js.map +1 -1
  84. package/lib/browser/view/debug-threads-widget.d.ts +6 -1
  85. package/lib/browser/view/debug-threads-widget.d.ts.map +1 -1
  86. package/lib/browser/view/debug-threads-widget.js +69 -5
  87. package/lib/browser/view/debug-threads-widget.js.map +1 -1
  88. package/lib/browser/view/debug-toolbar-widget.d.ts +5 -18
  89. package/lib/browser/view/debug-toolbar-widget.d.ts.map +1 -1
  90. package/lib/browser/view/debug-toolbar-widget.js +14 -58
  91. package/lib/browser/view/debug-toolbar-widget.js.map +1 -1
  92. package/lib/browser/view/debug-variables-source.d.ts.map +1 -1
  93. package/lib/browser/view/debug-variables-source.js +1 -0
  94. package/lib/browser/view/debug-variables-source.js.map +1 -1
  95. package/lib/browser/view/debug-variables-widget.d.ts +15 -0
  96. package/lib/browser/view/debug-variables-widget.d.ts.map +1 -1
  97. package/lib/browser/view/debug-variables-widget.js +56 -1
  98. package/lib/browser/view/debug-variables-widget.js.map +1 -1
  99. package/lib/browser/view/debug-view-model.d.ts +4 -0
  100. package/lib/browser/view/debug-view-model.d.ts.map +1 -1
  101. package/lib/browser/view/debug-view-model.js +18 -9
  102. package/lib/browser/view/debug-view-model.js.map +1 -1
  103. package/lib/browser/view/debug-watch-expression.d.ts.map +1 -1
  104. package/lib/browser/view/debug-watch-expression.js +4 -5
  105. package/lib/browser/view/debug-watch-expression.js.map +1 -1
  106. package/lib/browser/view/debug-watch-source.d.ts.map +1 -1
  107. package/lib/browser/view/debug-watch-source.js +1 -0
  108. package/lib/browser/view/debug-watch-source.js.map +1 -1
  109. package/lib/common/debug-preferences.d.ts +1 -0
  110. package/lib/common/debug-preferences.d.ts.map +1 -1
  111. package/lib/common/debug-preferences.js +11 -1
  112. package/lib/common/debug-preferences.js.map +1 -1
  113. package/package.json +16 -16
  114. package/src/browser/breakpoint/breakpoint-manager.ts +1 -1
  115. package/src/browser/console/debug-console-items.tsx +90 -21
  116. package/src/browser/console/debug-console-session.ts +1 -0
  117. package/src/browser/debug-commands.ts +402 -0
  118. package/src/browser/debug-frontend-application-contribution.ts +35 -410
  119. package/src/browser/debug-prefix-configuration.ts +1 -1
  120. package/src/browser/debug-session-contribution.ts +7 -2
  121. package/src/browser/debug-session-manager.ts +62 -9
  122. package/src/browser/debug-session.tsx +17 -4
  123. package/src/browser/editor/debug-hover-source.tsx +6 -2
  124. package/src/browser/editor/debug-inline-value-decorator.ts +1 -1
  125. package/src/browser/model/debug-breakpoint.tsx +12 -3
  126. package/src/browser/model/debug-data-breakpoint.tsx +21 -6
  127. package/src/browser/model/debug-function-breakpoint.tsx +22 -1
  128. package/src/browser/model/debug-instruction-breakpoint.tsx +16 -1
  129. package/src/browser/model/debug-source-breakpoint.tsx +24 -3
  130. package/src/browser/model/debug-stack-frame.tsx +13 -6
  131. package/src/browser/model/debug-thread.tsx +14 -10
  132. package/src/browser/style/index.css +30 -5
  133. package/src/browser/view/debug-action.tsx +3 -2
  134. package/src/browser/view/debug-breakpoints-source.tsx +5 -1
  135. package/src/browser/view/debug-configuration-widget.tsx +1 -1
  136. package/src/browser/view/debug-exception-breakpoint.tsx +30 -5
  137. package/src/browser/view/debug-session-widget.ts +0 -5
  138. package/src/browser/view/debug-threads-widget.ts +84 -6
  139. package/src/browser/view/debug-toolbar-widget.tsx +13 -60
  140. package/src/browser/view/debug-variables-source.ts +1 -0
  141. package/src/browser/view/debug-variables-widget.ts +59 -0
  142. package/src/browser/view/debug-view-model.ts +20 -8
  143. package/src/browser/view/debug-watch-expression.tsx +5 -6
  144. package/src/browser/view/debug-watch-source.ts +1 -0
  145. package/src/common/debug-preferences.ts +12 -1
@@ -69,14 +69,13 @@
69
69
  font-size: var(--theia-ui-font-size0);
70
70
  line-height: calc(var(--theia-private-horizontal-tab-height) / 2);
71
71
  border-radius: 2px;
72
+ margin-left: calc(var(--theia-ui-padding) / 2);
73
+ white-space: nowrap;
74
+ flex-shrink: 0;
72
75
  }
73
76
 
74
77
  .theia-data-breakpoint .theia-access-type {
75
- margin-left: var(--theia-ui-padding);
76
- }
77
-
78
- .theia-source-breakpoint > .theia-input {
79
- min-height: auto;
78
+ margin-left: var(--theia-ui-padding);
80
79
  }
81
80
 
82
81
  .theia-debug-session .status,
@@ -187,6 +186,7 @@
187
186
  }
188
187
 
189
188
  .theia-debug-console-variable {
189
+ display: flex;
190
190
  white-space: nowrap;
191
191
  overflow: hidden;
192
192
  text-overflow: ellipsis;
@@ -212,6 +212,22 @@
212
212
  color: var(--theia-variable-name-color);
213
213
  }
214
214
 
215
+ .theia-debug-console-variable .lazy-button {
216
+ margin-left: 3px;
217
+ border-radius: 5px;
218
+ cursor: pointer;
219
+ align-self: center;
220
+ color: var(--theia-icon-foreground);
221
+ }
222
+
223
+ .theia-debug-console-variable .lazy-button:hover {
224
+ background-color: var(--theia-toolbar-hoverBackground);
225
+ }
226
+
227
+ .theia-debug-console-variable .value {
228
+ margin-left: 6px;
229
+ }
230
+
215
231
  .theia-TreeNode:not(:hover) .theia-debug-console-variable .action-label {
216
232
  visibility: hidden;
217
233
  }
@@ -234,6 +250,15 @@
234
250
  display: flex;
235
251
  }
236
252
 
253
+ .theia-debug-breakpoint-actions {
254
+ display: none;
255
+ }
256
+
257
+ .theia-source-breakpoint:hover .theia-debug-breakpoint-actions {
258
+ display: flex;
259
+ align-items: center;
260
+ }
261
+
237
262
  /** Editor **/
238
263
 
239
264
  .theia-debug-breakpoint-icon {
@@ -21,7 +21,7 @@ import { MenuPath } from '@theia/core';
21
21
  export class DebugAction extends React.Component<DebugAction.Props> {
22
22
 
23
23
  override render(): React.ReactNode {
24
- const { enabled, label, iconClass } = this.props;
24
+ const { enabled, label, tooltip, iconClass } = this.props;
25
25
  const classNames = ['debug-action'];
26
26
  if (iconClass) {
27
27
  classNames.push(...codiconArray(iconClass, true));
@@ -31,7 +31,7 @@ export class DebugAction extends React.Component<DebugAction.Props> {
31
31
  }
32
32
  return <span tabIndex={0}
33
33
  className={classNames.join(' ')}
34
- title={label}
34
+ title={tooltip || label}
35
35
  onClick={() => { this.props.run([]); }}
36
36
  ref={this.setRef} >
37
37
  {!iconClass && <div>{label}</div>}
@@ -51,6 +51,7 @@ export class DebugAction extends React.Component<DebugAction.Props> {
51
51
  export namespace DebugAction {
52
52
  export interface Props {
53
53
  label: string
54
+ tooltip?: string
54
55
  iconClass: string
55
56
  run: (effectiveMenuPath: MenuPath) => void
56
57
  enabled?: boolean
@@ -19,6 +19,7 @@ import { TreeSource, TreeElement } from '@theia/core/lib/browser/source-tree';
19
19
  import { DebugViewModel } from './debug-view-model';
20
20
  import { BreakpointManager } from '../breakpoint/breakpoint-manager';
21
21
  import { DebugExceptionBreakpoint } from './debug-exception-breakpoint';
22
+ import { CommandService } from '@theia/core/lib/common';
22
23
 
23
24
  @injectable()
24
25
  export class DebugBreakpointsSource extends TreeSource {
@@ -29,6 +30,9 @@ export class DebugBreakpointsSource extends TreeSource {
29
30
  @inject(BreakpointManager)
30
31
  protected readonly breakpoints: BreakpointManager;
31
32
 
33
+ @inject(CommandService)
34
+ protected readonly commandService: CommandService;
35
+
32
36
  @postConstruct()
33
37
  protected init(): void {
34
38
  this.fireDidChange();
@@ -37,7 +41,7 @@ export class DebugBreakpointsSource extends TreeSource {
37
41
 
38
42
  *getElements(): IterableIterator<TreeElement> {
39
43
  for (const exceptionBreakpoint of this.breakpoints.getExceptionBreakpoints()) {
40
- yield new DebugExceptionBreakpoint(exceptionBreakpoint, this.breakpoints);
44
+ yield new DebugExceptionBreakpoint(exceptionBreakpoint, this.breakpoints, this.commandService);
41
45
  }
42
46
  yield* this.model.dataBreakpoints;
43
47
  yield* this.model.functionBreakpoints;
@@ -21,7 +21,7 @@ import * as React from '@theia/core/shared/react';
21
21
  import { WorkspaceService } from '@theia/workspace/lib/browser';
22
22
  import { DebugConsoleContribution } from '../console/debug-console-contribution';
23
23
  import { DebugConfigurationManager } from '../debug-configuration-manager';
24
- import { DebugCommands } from '../debug-frontend-application-contribution';
24
+ import { DebugCommands } from '../debug-commands';
25
25
  import { DebugSessionManager } from '../debug-session-manager';
26
26
  import { DebugAction } from './debug-action';
27
27
  import { DebugConfigurationSelect } from './debug-configuration-select';
@@ -18,22 +18,26 @@ import * as React from '@theia/core/shared/react';
18
18
  import { TreeElement } from '@theia/core/lib/browser/source-tree';
19
19
  import { BreakpointManager } from '../breakpoint/breakpoint-manager';
20
20
  import { ExceptionBreakpoint } from '../breakpoint/breakpoint-marker';
21
- import { SingleTextInputDialog } from '@theia/core/lib/browser/dialogs';
22
- import { TREE_NODE_INFO_CLASS } from '@theia/core/lib/browser';
23
- import { nls } from '@theia/core';
21
+ import { SingleTextInputDialog, TREE_NODE_INFO_CLASS, codicon, TreeWidget } from '@theia/core/lib/browser';
22
+ import { SelectableTreeNode } from '@theia/core/lib/browser/tree/tree-selection';
23
+ import { nls, CommandService } from '@theia/core';
24
+ import { DebugCommands } from '../debug-commands';
24
25
 
25
26
  export class DebugExceptionBreakpoint implements TreeElement {
26
27
 
27
28
  readonly id: string;
29
+ protected treeWidget?: TreeWidget;
28
30
 
29
31
  constructor(
30
32
  readonly data: ExceptionBreakpoint,
31
- readonly breakpoints: BreakpointManager
33
+ readonly breakpoints: BreakpointManager,
34
+ protected readonly commandService: CommandService
32
35
  ) {
33
36
  this.id = data.raw.filter + ':' + data.raw.label;
34
37
  }
35
38
 
36
- render(): React.ReactNode {
39
+ render(host: TreeWidget): React.ReactNode {
40
+ this.treeWidget = host;
37
41
  return <div title={this.data.raw.description || this.data.raw.label} className='theia-source-breakpoint'>
38
42
  <span className='theia-debug-breakpoint-icon' />
39
43
  <input type='checkbox' checked={this.data.enabled} onChange={this.toggle} />
@@ -43,9 +47,30 @@ export class DebugExceptionBreakpoint implements TreeElement {
43
47
  <span title={nls.localizeByDefault('Expression condition: {0}', this.data.condition)}
44
48
  className={'path ' + TREE_NODE_INFO_CLASS}>{this.data.condition} </span>}
45
49
  </span>
50
+ {this.renderActions()}
46
51
  </div>;
47
52
  }
48
53
 
54
+ protected renderActions(): React.ReactNode {
55
+ if (this.data.raw.supportsCondition) {
56
+ return <div className='theia-debug-breakpoint-actions'>
57
+ <div className={codicon('edit', true)} title={nls.localizeByDefault('Edit Condition...')} onClick={this.onEdit} />
58
+ </div>;
59
+ }
60
+ return undefined;
61
+ }
62
+
63
+ protected onEdit = async () => {
64
+ await this.selectInTree();
65
+ this.commandService.executeCommand(DebugCommands.EDIT_BREAKPOINT_CONDITION.id);
66
+ };
67
+
68
+ protected async selectInTree(): Promise<void> {
69
+ if (this.treeWidget?.model && SelectableTreeNode.is(this)) {
70
+ this.treeWidget.model.selectNode(this);
71
+ }
72
+ }
73
+
49
74
  protected toggle = () => this.breakpoints.toggleExceptionBreakpoint(this.data.raw.filter);
50
75
 
51
76
  async editCondition(): Promise<void> {
@@ -100,11 +100,6 @@ export class DebugSessionWidget extends BaseWidget implements StatefulWidget, Ap
100
100
  layout.addWidget(this.viewContainer);
101
101
  }
102
102
 
103
- protected override onActivateRequest(msg: Message): void {
104
- super.onActivateRequest(msg);
105
- this.toolbar.focus();
106
- }
107
-
108
103
  protected override onAfterShow(msg: Message): void {
109
104
  super.onAfterShow(msg);
110
105
  this.getTrackableWidgets().forEach(w => w.update());
@@ -16,8 +16,9 @@
16
16
 
17
17
  import { injectable, inject, postConstruct, interfaces, Container } from '@theia/core/shared/inversify';
18
18
  import { MenuPath } from '@theia/core';
19
- import { TreeNode, NodeProps, SelectableTreeNode } from '@theia/core/lib/browser';
19
+ import { TreeNode, NodeProps, SelectableTreeNode, CompositeTreeNode } from '@theia/core/lib/browser';
20
20
  import { SourceTreeWidget, TreeElementNode } from '@theia/core/lib/browser/source-tree';
21
+ import { ExpandableTreeNode } from '@theia/core/lib/browser/tree/tree-expansion';
21
22
  import { DebugThreadsSource } from './debug-threads-source';
22
23
  import { DebugSession } from '../debug-session';
23
24
  import { DebugThread } from '../model/debug-thread';
@@ -65,28 +66,105 @@ export class DebugThreadsWidget extends SourceTreeWidget {
65
66
  this.toDispose.push(this.threads);
66
67
  this.source = this.threads;
67
68
 
68
- this.toDispose.push(this.viewModel.onDidChange(() => this.updateWidgetSelection()));
69
+ this.toDispose.push(this.viewModel.onDidChange(() => {
70
+ this.updateWidgetSelection();
71
+ }));
69
72
  this.toDispose.push(this.model.onSelectionChanged(() => this.updateModelSelection()));
70
73
  }
71
74
 
72
75
  protected updatingSelection = false;
73
- protected updateWidgetSelection(): void {
76
+ protected async updateWidgetSelection(): Promise<void> {
74
77
  if (this.updatingSelection) {
75
78
  return;
76
79
  }
77
80
  this.updatingSelection = true;
78
81
  try {
82
+ await this.model.refresh();
83
+
79
84
  const { currentThread } = this.viewModel;
80
- if (currentThread) {
81
- const node = this.model.getNode(currentThread.id);
82
- if (SelectableTreeNode.is(node)) {
85
+
86
+ // Check if current selection still exists in the tree
87
+ const selectedNode = this.model.selectedNodes[0];
88
+ const selectionStillValid = selectedNode && this.model.getNode(selectedNode.id);
89
+
90
+ // Only update selection if:
91
+ // 1. Current selection is invalid (node no longer in tree), OR
92
+ // 2. There's a stopped thread to show
93
+ if (selectionStillValid && (!currentThread || !currentThread.stopped)) {
94
+ return;
95
+ }
96
+
97
+ // Try to select the current stopped thread, or clear if none
98
+ if (currentThread && currentThread.stopped) {
99
+ const node = await this.waitForNode(currentThread);
100
+
101
+ // Re-check stopped state after async wait
102
+ if (!currentThread.stopped) {
103
+ return;
104
+ }
105
+
106
+ if (node && SelectableTreeNode.is(node)) {
83
107
  this.model.selectNode(node);
108
+
109
+ // Set context key
110
+ if (TreeElementNode.is(node)) {
111
+ if (node.element instanceof DebugThread) {
112
+ this.debugCallStackItemTypeKey.set('thread');
113
+ } else if (node.element instanceof DebugSession) {
114
+ this.debugCallStackItemTypeKey.set('session');
115
+ }
116
+ }
84
117
  }
118
+ } else if (!selectionStillValid) {
119
+ // Selection is stale and no stopped thread to select
120
+ this.model.clearSelection();
85
121
  }
86
122
  } finally {
87
123
  this.updatingSelection = false;
88
124
  }
89
125
  }
126
+
127
+ /**
128
+ * Wait for a node to appear in the tree, expanding root children to populate the tree
129
+ */
130
+ protected async waitForNode(thread: DebugThread): Promise<TreeNode | undefined> {
131
+ const maxAttempts = 10;
132
+ const delayMs = 50;
133
+ const threadId = thread.id;
134
+
135
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
136
+ // If thread continued during wait, abort
137
+ if (!thread.stopped) {
138
+ return undefined;
139
+ }
140
+
141
+ await this.model.refresh();
142
+
143
+ const root = this.model.root;
144
+
145
+ // Expand all root's direct children to populate the tree
146
+ if (root && CompositeTreeNode.is(root)) {
147
+ for (const child of root.children) {
148
+ if (ExpandableTreeNode.is(child) && !child.expanded) {
149
+ await this.model.expandNode(child);
150
+ }
151
+ }
152
+ }
153
+
154
+ // Now look directly for the thread node
155
+ const threadNode = this.model.getNode(threadId);
156
+ if (threadNode) {
157
+ return threadNode;
158
+ }
159
+
160
+ if (attempt < maxAttempts - 1) {
161
+ await new Promise(resolve => setTimeout(resolve, delayMs));
162
+ }
163
+ }
164
+
165
+ return undefined;
166
+ }
167
+
90
168
  protected updateModelSelection(): void {
91
169
  if (this.updatingSelection) {
92
170
  return;
@@ -16,69 +16,38 @@
16
16
 
17
17
  import * as React from '@theia/core/shared/react';
18
18
  import { inject, postConstruct, injectable } from '@theia/core/shared/inversify';
19
- import { CommandMenu, CommandRegistry, CompoundMenuNode, Disposable, DisposableCollection, MenuModelRegistry, MenuPath } from '@theia/core';
19
+ import { CommandMenu, CompoundMenuNode, MenuModelRegistry, MenuPath } from '@theia/core';
20
+ import { KeybindingRegistry } from '@theia/core/lib/browser';
20
21
  import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
21
22
  import { ReactWidget } from '@theia/core/lib/browser/widgets';
22
23
  import { DebugViewModel } from './debug-view-model';
23
- import { DebugState } from '../debug-session';
24
24
  import { DebugAction } from './debug-action';
25
- import { nls } from '@theia/core/lib/common/nls';
26
25
 
27
26
  @injectable()
28
27
  export class DebugToolBar extends ReactWidget {
29
28
 
30
29
  static readonly MENU: MenuPath = ['debug-toolbar-menu'];
30
+ static readonly CONTROLS: MenuPath = [...DebugToolBar.MENU, 'z_controls'];
31
31
 
32
- @inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry;
33
32
  @inject(MenuModelRegistry) protected readonly menuModelRegistry: MenuModelRegistry;
33
+ @inject(KeybindingRegistry) protected readonly keybindingRegistry: KeybindingRegistry;
34
34
  @inject(ContextKeyService) protected readonly contextKeyService: ContextKeyService;
35
35
  @inject(DebugViewModel) protected readonly model: DebugViewModel;
36
36
 
37
- protected readonly onRender = new DisposableCollection();
38
-
39
37
  @postConstruct()
40
38
  protected init(): void {
41
39
  this.id = 'debug:toolbar:' + this.model.id;
42
40
  this.addClass('debug-toolbar');
43
41
  this.toDispose.push(this.model);
44
42
  this.toDispose.push(this.model.onDidChange(() => this.update()));
43
+ this.toDispose.push(this.keybindingRegistry.onKeybindingsChanged(() => this.update()));
45
44
  this.scrollOptions = undefined;
46
45
  this.update();
47
46
  }
48
47
 
49
- focus(): void {
50
- if (!this.doFocus()) {
51
- this.onRender.push(Disposable.create(() => this.doFocus()));
52
- this.update();
53
- }
54
- }
55
- protected doFocus(): boolean {
56
- if (!this.stepRef) {
57
- return false;
58
- }
59
- this.stepRef.focus();
60
- return true;
61
- }
62
- protected stepRef: DebugAction | undefined;
63
- protected setStepRef = (stepRef: DebugAction | null) => {
64
- this.stepRef = stepRef || undefined;
65
- this.onRender.dispose();
66
- };
67
-
68
48
  protected render(): React.ReactNode {
69
- const { state } = this.model;
70
49
  return <React.Fragment>
71
50
  {this.renderContributedCommands()}
72
- {this.renderContinue()}
73
- <DebugAction enabled={state === DebugState.Stopped} run={this.stepOver} label={nls.localizeByDefault('Step Over')}
74
- iconClass='debug-step-over' ref={this.setStepRef} />
75
- <DebugAction enabled={state === DebugState.Stopped} run={this.stepIn} label={nls.localizeByDefault('Step Into')}
76
- iconClass='debug-step-into' />
77
- <DebugAction enabled={state === DebugState.Stopped} run={this.stepOut} label={nls.localizeByDefault('Step Out')}
78
- iconClass='debug-step-out' />
79
- <DebugAction enabled={state !== DebugState.Inactive} run={this.restart} label={nls.localizeByDefault('Restart')}
80
- iconClass='debug-restart' />
81
- {this.renderStart()}
82
51
  </React.Fragment>;
83
52
  }
84
53
 
@@ -99,36 +68,20 @@ export class DebugToolBar extends ReactWidget {
99
68
  }
100
69
 
101
70
  protected debugAction(commandMenuNode: CommandMenu): React.ReactNode {
71
+ const accelerator = this.acceleratorFor(commandMenuNode.id);
72
+ const run = (effectiveMenuPath: MenuPath) => commandMenuNode.run(effectiveMenuPath).catch(e => console.error(e));
102
73
  return <DebugAction
103
74
  key={commandMenuNode.id}
104
- enabled={true}
75
+ enabled={commandMenuNode.isEnabled(DebugToolBar.MENU)}
105
76
  label={commandMenuNode.label}
77
+ tooltip={commandMenuNode.label + (accelerator ? ` (${accelerator})` : '')}
106
78
  iconClass={commandMenuNode.icon || ''}
107
- run={commandMenuNode.run} />;
79
+ run={run} />;
108
80
  }
109
81
 
110
- protected renderStart(): React.ReactNode {
111
- const { state } = this.model;
112
- if (state === DebugState.Inactive && this.model.sessionCount === 1) {
113
- return <DebugAction run={this.start} label={nls.localizeByDefault('Start')} iconClass='debug-start' />;
114
- }
115
- return <DebugAction enabled={state !== DebugState.Inactive} run={this.stop} label={nls.localizeByDefault('Stop')} iconClass='debug-stop' />;
116
- }
117
- protected renderContinue(): React.ReactNode {
118
- const { state } = this.model;
119
- if (state === DebugState.Stopped) {
120
- return <DebugAction run={this.continue} label={nls.localizeByDefault('Continue')} iconClass='debug-continue' />;
121
- }
122
- return <DebugAction enabled={state === DebugState.Running} run={this.pause} label={nls.localizeByDefault('Pause')} iconClass='debug-pause' />;
82
+ protected acceleratorFor(commandId: string): string | undefined {
83
+ const keybindings = this.keybindingRegistry.getKeybindingsForCommand(commandId);
84
+ return keybindings.length ? this.keybindingRegistry.acceleratorFor(keybindings[0], '+').join(' ') : undefined;
123
85
  }
124
86
 
125
- protected start = () => this.model.start({ startedByUser: true });
126
- protected restart = () => this.model.restart();
127
- protected stop = () => this.model.terminate();
128
- protected continue = () => this.model.currentThread && this.model.currentThread.continue();
129
- protected pause = () => this.model.currentThread && this.model.currentThread.pause();
130
- protected stepOver = () => this.model.currentThread && this.model.currentThread.stepOver();
131
- protected stepIn = () => this.model.currentThread && this.model.currentThread.stepIn();
132
- protected stepOut = () => this.model.currentThread && this.model.currentThread.stepOut();
133
-
134
87
  }
@@ -30,6 +30,7 @@ export class DebugVariablesSource extends TreeSource {
30
30
  protected init(): void {
31
31
  this.refresh();
32
32
  this.toDispose.push(this.model.onDidChange(() => this.refresh()));
33
+ this.toDispose.push(this.model.onDidResolveLazyVariable(() => this.fireDidChange()));
33
34
  }
34
35
 
35
36
  protected readonly refresh = debounce(() => this.fireDidChange(), 400);
@@ -25,6 +25,9 @@ import { SelectableTreeNode, TreeNode, TreeSelection } from '@theia/core/lib/bro
25
25
  import { DebugVariable } from '../console/debug-console-items';
26
26
  import { BreakpointManager } from '../breakpoint/breakpoint-manager';
27
27
  import { DataBreakpoint, DataBreakpointSource, DataBreakpointSourceType } from '../breakpoint/breakpoint-marker';
28
+ import { DebugSessionManager } from '../debug-session-manager';
29
+ import { DebugSession } from '../debug-session';
30
+ import { DebugStackFrame } from '../model/debug-stack-frame';
28
31
 
29
32
  @injectable()
30
33
  export class DebugVariablesWidget extends SourceTreeWidget {
@@ -64,6 +67,12 @@ export class DebugVariablesWidget extends SourceTreeWidget {
64
67
  @inject(BreakpointManager)
65
68
  protected readonly breakpointManager: BreakpointManager;
66
69
 
70
+ @inject(DebugSessionManager)
71
+ protected readonly sessionManager: DebugSessionManager;
72
+
73
+ protected stackFrame: DebugStackFrame | undefined;
74
+ protected readonly statePerSession = new Map<string, DebugVariablesWidgetSessionState>();
75
+
67
76
  @postConstruct()
68
77
  protected override init(): void {
69
78
  super.init();
@@ -71,6 +80,44 @@ export class DebugVariablesWidget extends SourceTreeWidget {
71
80
  this.title.label = nls.localizeByDefault('Variables');
72
81
  this.toDispose.push(this.variables);
73
82
  this.source = this.variables;
83
+ this.toDispose.push(this.sessionManager.onDidFocusStackFrame(stackFrame => this.handleDidFocusStackFrame(stackFrame)));
84
+ this.toDispose.push(this.sessionManager.onDidDestroyDebugSession(session => this.handleDidDestroyDebugSession(session)));
85
+ }
86
+
87
+ protected handleDidFocusStackFrame(stackFrame: DebugStackFrame | undefined): void {
88
+ if (this.stackFrame !== stackFrame) {
89
+ if (this.stackFrame) {
90
+ const sessionState = this.getOrCreateSessionState(this.stackFrame.session);
91
+ sessionState.setStateForStackFrame(this.stackFrame, this.superStoreState());
92
+ }
93
+ if (stackFrame) {
94
+ const sessionState = this.statePerSession.get(stackFrame.session.id);
95
+ if (sessionState) {
96
+ const state = sessionState.getStateForStackFrame(stackFrame);
97
+ if (state) {
98
+ this.superRestoreState(state);
99
+ }
100
+ }
101
+ }
102
+ this.stackFrame = stackFrame;
103
+ }
104
+ }
105
+
106
+ protected getOrCreateSessionState(session: DebugSession): DebugVariablesWidgetSessionState {
107
+ let sessionState = this.statePerSession.get(session.id);
108
+ if (!sessionState) {
109
+ sessionState = this.newSessionState();
110
+ this.statePerSession.set(session.id, sessionState);
111
+ }
112
+ return sessionState;
113
+ }
114
+
115
+ protected newSessionState(): DebugVariablesWidgetSessionState {
116
+ return new DebugVariablesWidgetSessionState();
117
+ }
118
+
119
+ protected handleDidDestroyDebugSession(session: DebugSession): void {
120
+ this.statePerSession.delete(session.id);
74
121
  }
75
122
 
76
123
  protected override handleContextMenuEvent(node: TreeNode | undefined, event: MouseEvent<HTMLElement>): void {
@@ -156,3 +203,15 @@ export class DebugVariablesWidget extends SourceTreeWidget {
156
203
  );
157
204
  }
158
205
  }
206
+
207
+ export class DebugVariablesWidgetSessionState {
208
+ protected readonly statePerStackFrame = new Map<string, object>();
209
+
210
+ setStateForStackFrame(stackFrame: DebugStackFrame, state: object): void {
211
+ this.statePerStackFrame.set(stackFrame.id, state);
212
+ }
213
+
214
+ getStateForStackFrame(stackFrame: DebugStackFrame): object | undefined {
215
+ return this.statePerStackFrame.get(stackFrame.id);
216
+ }
217
+ }
@@ -29,6 +29,7 @@ import { DebugFunctionBreakpoint } from '../model/debug-function-breakpoint';
29
29
  import { DebugInstructionBreakpoint } from '../model/debug-instruction-breakpoint';
30
30
  import { DebugSessionOptionsBase } from '../debug-session-options';
31
31
  import { DebugDataBreakpoint } from '../model/debug-data-breakpoint';
32
+ import { DebugVariable } from '../console/debug-console-items';
32
33
 
33
34
  @injectable()
34
35
  export class DebugViewModel implements Disposable {
@@ -46,6 +47,12 @@ export class DebugViewModel implements Disposable {
46
47
  this.onDidChangeBreakpointsEmitter.fire(uri);
47
48
  }
48
49
 
50
+ protected readonly onDidResolveLazyVariableEmitter = new Emitter<DebugVariable>();
51
+ readonly onDidResolveLazyVariable: Event<DebugVariable> = this.onDidResolveLazyVariableEmitter.event;
52
+ protected fireDidResolveLazyVariable(variable: DebugVariable): void {
53
+ this.onDidResolveLazyVariableEmitter.fire(variable);
54
+ }
55
+
49
56
  protected readonly _watchExpressions = new Map<number, DebugWatchExpression>();
50
57
 
51
58
  protected readonly onDidChangeWatchExpressionsEmitter = new Emitter<void>();
@@ -57,6 +64,7 @@ export class DebugViewModel implements Disposable {
57
64
  protected readonly toDispose = new DisposableCollection(
58
65
  this.onDidChangeEmitter,
59
66
  this.onDidChangeBreakpointsEmitter,
67
+ this.onDidResolveLazyVariableEmitter,
60
68
  this.onDidChangeWatchExpressionsEmitter,
61
69
  );
62
70
 
@@ -88,13 +96,17 @@ export class DebugViewModel implements Disposable {
88
96
  this.fireDidChange();
89
97
  }));
90
98
  this.toDispose.push(this.manager.onDidChange(current => {
91
- if (current === this.currentSession) {
92
- this.fireDidChange();
93
- }
99
+ // Always fire change to update views, even if session is not current
100
+ // This ensures threads view updates for all sessions
101
+ this.fireDidChange();
94
102
  }));
95
103
  this.toDispose.push(this.manager.onDidChangeBreakpoints(({ session, uri }) => {
96
- if (!session || session === this.currentSession) {
97
- this.fireDidChangeBreakpoints(uri);
104
+ // Fire for all sessions since we now show breakpoints from all active sessions
105
+ this.fireDidChangeBreakpoints(uri);
106
+ }));
107
+ this.toDispose.push(this.manager.onDidResolveLazyVariable(({ session, variable }) => {
108
+ if (session === this.currentSession) {
109
+ this.fireDidResolveLazyVariable(variable);
98
110
  }
99
111
  }));
100
112
  this.updateWatchExpressions();
@@ -127,15 +139,15 @@ export class DebugViewModel implements Disposable {
127
139
  }
128
140
 
129
141
  get breakpoints(): DebugSourceBreakpoint[] {
130
- return this.manager.getBreakpoints(this.currentSession);
142
+ return this.manager.getBreakpoints();
131
143
  }
132
144
 
133
145
  get functionBreakpoints(): DebugFunctionBreakpoint[] {
134
- return this.manager.getFunctionBreakpoints(this.currentSession);
146
+ return this.manager.getFunctionBreakpoints();
135
147
  }
136
148
 
137
149
  get instructionBreakpoints(): DebugInstructionBreakpoint[] {
138
- return this.manager.getInstructionBreakpoints(this.currentSession);
150
+ return this.manager.getInstructionBreakpoints();
139
151
  }
140
152
 
141
153
  get dataBreakpoints(): DebugDataBreakpoint[] {
@@ -23,7 +23,7 @@ import { nls } from '@theia/core';
23
23
 
24
24
  export class DebugWatchExpression extends ExpressionItem {
25
25
 
26
- readonly id: number;
26
+ override readonly id: number;
27
27
  protected isError: boolean;
28
28
  protected isNotAvailable: boolean;
29
29
 
@@ -34,12 +34,12 @@ export class DebugWatchExpression extends ExpressionItem {
34
34
  remove: () => void,
35
35
  onDidChange: () => void
36
36
  }) {
37
- super(options.expression, options.session);
38
- this.id = options.id;
37
+ super(options.expression, options.session, options.id);
39
38
  }
40
39
 
41
40
  override async evaluate(): Promise<void> {
42
41
  await super.evaluate('watch');
42
+ this.options.onDidChange();
43
43
  }
44
44
 
45
45
  protected override setResult(body?: DebugProtocol.EvaluateResponse['body'], error?: string): void {
@@ -56,14 +56,13 @@ export class DebugWatchExpression extends ExpressionItem {
56
56
  super.setResult(body, error);
57
57
  this.isError = !!error;
58
58
  }
59
- this.options.onDidChange();
60
59
  }
61
60
 
62
61
  override render(): React.ReactNode {
63
62
  const valueClass = this.valueClass();
64
63
  return <div className='theia-debug-console-variable theia-debug-watch-expression'>
65
64
  <div className={TREE_NODE_SEGMENT_GROW_CLASS}>
66
- <span title={this.type || this._expression} className='name'>{this._expression}: </span>
65
+ <span title={this.type || this._expression} className='name'>{this._expression}:</span>
67
66
  <span title={this._value} ref={this.setValueRef} className={valueClass}>{this._value}</span>
68
67
  </div>
69
68
  <div className={codicon('close', true)} title={nls.localizeByDefault('Remove Expression')} onClick={this.options.remove} />
@@ -77,7 +76,7 @@ export class DebugWatchExpression extends ExpressionItem {
77
76
  if (this.isNotAvailable) {
78
77
  return 'watch-not-available';
79
78
  }
80
- return '';
79
+ return 'value';
81
80
  }
82
81
 
83
82
  async open(): Promise<void> {
@@ -30,6 +30,7 @@ export class DebugWatchSource extends TreeSource {
30
30
  protected init(): void {
31
31
  this.refresh();
32
32
  this.toDispose.push(this.model.onDidChangeWatchExpressions(() => this.refresh()));
33
+ this.toDispose.push(this.model.onDidResolveLazyVariable(() => this.refresh()));
33
34
  }
34
35
 
35
36
  protected readonly refresh = debounce(() => this.fireDidChange(), 100);