@theia/debug 1.67.0-next.3 → 1.67.0-next.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/browser/breakpoint/breakpoint-manager.d.ts +21 -1
- package/lib/browser/breakpoint/breakpoint-manager.d.ts.map +1 -1
- package/lib/browser/breakpoint/breakpoint-manager.js +63 -17
- package/lib/browser/breakpoint/breakpoint-manager.js.map +1 -1
- package/lib/browser/breakpoint/breakpoint-marker.d.ts +23 -0
- package/lib/browser/breakpoint/breakpoint-marker.d.ts.map +1 -1
- package/lib/browser/breakpoint/breakpoint-marker.js +15 -1
- package/lib/browser/breakpoint/breakpoint-marker.js.map +1 -1
- package/lib/browser/breakpoint/debug-data-breakpoint-actions.d.ts +17 -0
- package/lib/browser/breakpoint/debug-data-breakpoint-actions.d.ts.map +1 -0
- package/lib/browser/breakpoint/debug-data-breakpoint-actions.js +166 -0
- package/lib/browser/breakpoint/debug-data-breakpoint-actions.js.map +1 -0
- package/lib/browser/console/debug-console-items.d.ts +1 -0
- package/lib/browser/console/debug-console-items.d.ts.map +1 -1
- package/lib/browser/console/debug-console-items.js +4 -1
- package/lib/browser/console/debug-console-items.js.map +1 -1
- package/lib/browser/debug-frontend-application-contribution.d.ts +6 -0
- package/lib/browser/debug-frontend-application-contribution.d.ts.map +1 -1
- package/lib/browser/debug-frontend-application-contribution.js +54 -3
- package/lib/browser/debug-frontend-application-contribution.js.map +1 -1
- package/lib/browser/debug-frontend-module.d.ts.map +1 -1
- package/lib/browser/debug-frontend-module.js +4 -0
- package/lib/browser/debug-frontend-module.js.map +1 -1
- package/lib/browser/debug-session-manager.d.ts +2 -0
- package/lib/browser/debug-session-manager.d.ts.map +1 -1
- package/lib/browser/debug-session-manager.js +17 -0
- package/lib/browser/debug-session-manager.js.map +1 -1
- package/lib/browser/debug-session.d.ts +3 -0
- package/lib/browser/debug-session.d.ts.map +1 -1
- package/lib/browser/debug-session.js +33 -0
- package/lib/browser/debug-session.js.map +1 -1
- package/lib/browser/editor/debug-expression-provider.d.ts +5 -0
- package/lib/browser/editor/debug-expression-provider.d.ts.map +1 -1
- package/lib/browser/editor/debug-expression-provider.js +34 -4
- package/lib/browser/editor/debug-expression-provider.js.map +1 -1
- package/lib/browser/editor/debug-hover-widget.d.ts.map +1 -1
- package/lib/browser/editor/debug-hover-widget.js +4 -37
- package/lib/browser/editor/debug-hover-widget.js.map +1 -1
- package/lib/browser/model/debug-data-breakpoint.d.ts +16 -0
- package/lib/browser/model/debug-data-breakpoint.d.ts.map +1 -0
- package/lib/browser/model/debug-data-breakpoint.js +75 -0
- package/lib/browser/model/debug-data-breakpoint.js.map +1 -0
- package/lib/browser/view/debug-breakpoints-source.d.ts.map +1 -1
- package/lib/browser/view/debug-breakpoints-source.js +4 -9
- package/lib/browser/view/debug-breakpoints-source.js.map +1 -1
- package/lib/browser/view/debug-stack-frames-widget.d.ts.map +1 -1
- package/lib/browser/view/debug-stack-frames-widget.js +7 -2
- package/lib/browser/view/debug-stack-frames-widget.js.map +1 -1
- package/lib/browser/view/debug-variables-widget.d.ts +13 -2
- package/lib/browser/view/debug-variables-widget.d.ts.map +1 -1
- package/lib/browser/view/debug-variables-widget.js +81 -0
- package/lib/browser/view/debug-variables-widget.js.map +1 -1
- package/lib/browser/view/debug-view-model.d.ts +2 -0
- package/lib/browser/view/debug-view-model.d.ts.map +1 -1
- package/lib/browser/view/debug-view-model.js +3 -0
- package/lib/browser/view/debug-view-model.js.map +1 -1
- package/package.json +15 -15
- package/src/browser/breakpoint/breakpoint-manager.ts +69 -16
- package/src/browser/breakpoint/breakpoint-marker.ts +38 -0
- package/src/browser/breakpoint/debug-data-breakpoint-actions.ts +170 -0
- package/src/browser/console/debug-console-items.tsx +5 -1
- package/src/browser/debug-frontend-application-contribution.ts +52 -0
- package/src/browser/debug-frontend-module.ts +4 -0
- package/src/browser/debug-session-manager.ts +18 -0
- package/src/browser/debug-session.tsx +31 -0
- package/src/browser/editor/debug-expression-provider.ts +49 -4
- package/src/browser/editor/debug-hover-widget.ts +7 -52
- package/src/browser/model/debug-data-breakpoint.tsx +79 -0
- package/src/browser/style/index.css +4 -0
- package/src/browser/view/debug-breakpoints-source.tsx +4 -10
- package/src/browser/view/debug-stack-frames-widget.ts +6 -2
- package/src/browser/view/debug-variables-widget.ts +99 -2
- package/src/browser/view/debug-view-model.ts +5 -0
|
@@ -46,6 +46,7 @@ import { DebugInstructionBreakpoint } from './model/debug-instruction-breakpoint
|
|
|
46
46
|
import { nls } from '@theia/core';
|
|
47
47
|
import { TestService, TestServices } from '@theia/test/lib/browser/test-service';
|
|
48
48
|
import { DebugSessionManager } from './debug-session-manager';
|
|
49
|
+
import { DebugDataBreakpoint } from './model/debug-data-breakpoint';
|
|
49
50
|
|
|
50
51
|
export enum DebugState {
|
|
51
52
|
Inactive,
|
|
@@ -624,6 +625,14 @@ export class DebugSession implements CompositeTreeElement {
|
|
|
624
625
|
return this.breakpoints.getInstructionBreakpoints().map(origin => new DebugInstructionBreakpoint(origin, this.asDebugBreakpointOptions()));
|
|
625
626
|
}
|
|
626
627
|
|
|
628
|
+
getDataBreakpoints(): DebugDataBreakpoint[] {
|
|
629
|
+
if (this.capabilities.supportsDataBreakpoints) {
|
|
630
|
+
return this.getBreakpoints(BreakpointManager.DATA_URI)
|
|
631
|
+
.filter((breakpoint): breakpoint is DebugDataBreakpoint => breakpoint instanceof DebugDataBreakpoint);
|
|
632
|
+
}
|
|
633
|
+
return this.breakpoints.getDataBreakpoints().map(origin => new DebugDataBreakpoint(origin, this.asDebugBreakpointOptions()));
|
|
634
|
+
}
|
|
635
|
+
|
|
627
636
|
getBreakpoints(uri?: URI): DebugBreakpoint[] {
|
|
628
637
|
if (uri) {
|
|
629
638
|
return this._breakpoints.get(uri.toString()) || [];
|
|
@@ -730,6 +739,8 @@ export class DebugSession implements CompositeTreeElement {
|
|
|
730
739
|
await this.sendFunctionBreakpoints(affectedUri);
|
|
731
740
|
} else if (affectedUri.toString() === BreakpointManager.INSTRUCTION_URI.toString()) {
|
|
732
741
|
await this.sendInstructionBreakpoints();
|
|
742
|
+
} else if (affectedUri.isEqual(BreakpointManager.DATA_URI)) {
|
|
743
|
+
await this.sendDataBreakpoints();
|
|
733
744
|
} else {
|
|
734
745
|
await this.sendSourceBreakpoints(affectedUri, sourceModified);
|
|
735
746
|
}
|
|
@@ -855,6 +866,25 @@ export class DebugSession implements CompositeTreeElement {
|
|
|
855
866
|
this.setBreakpoints(BreakpointManager.INSTRUCTION_URI, all);
|
|
856
867
|
}
|
|
857
868
|
|
|
869
|
+
protected async sendDataBreakpoints(): Promise<void> {
|
|
870
|
+
if (!this.capabilities.supportsDataBreakpoints) { return; }
|
|
871
|
+
const known = this._breakpoints.get(BreakpointManager.DATA_URI.toString());
|
|
872
|
+
const all = this.breakpoints.getDataBreakpoints().map<DebugDataBreakpoint>(bp =>
|
|
873
|
+
known?.find((candidate): candidate is DebugDataBreakpoint => candidate instanceof DebugDataBreakpoint && candidate.id === bp.id)
|
|
874
|
+
?? new DebugDataBreakpoint(bp, this.asDebugBreakpointOptions())
|
|
875
|
+
);
|
|
876
|
+
const enabled = all.filter(bp => bp.enabled);
|
|
877
|
+
try {
|
|
878
|
+
const response = await this.sendRequest('setDataBreakpoints', {
|
|
879
|
+
breakpoints: enabled.map(({ origin }) => origin.raw)
|
|
880
|
+
});
|
|
881
|
+
response.body.breakpoints.forEach((raw, index) => enabled[index].update({ raw }));
|
|
882
|
+
} catch {
|
|
883
|
+
enabled.forEach(breakpoint => breakpoint.update({ raw: { verified: false } }));
|
|
884
|
+
}
|
|
885
|
+
this.setBreakpoints(BreakpointManager.DATA_URI, all);
|
|
886
|
+
}
|
|
887
|
+
|
|
858
888
|
protected setBreakpoints(uri: URI, breakpoints: DebugBreakpoint[]): void {
|
|
859
889
|
this._breakpoints.set(uri.toString(), breakpoints);
|
|
860
890
|
this.fireDidChangeBreakpoints(uri);
|
|
@@ -890,6 +920,7 @@ export class DebugSession implements CompositeTreeElement {
|
|
|
890
920
|
}
|
|
891
921
|
yield BreakpointManager.FUNCTION_URI;
|
|
892
922
|
yield BreakpointManager.EXCEPTION_URI;
|
|
923
|
+
yield BreakpointManager.DATA_URI;
|
|
893
924
|
}
|
|
894
925
|
}
|
|
895
926
|
|
|
@@ -19,21 +19,66 @@
|
|
|
19
19
|
*--------------------------------------------------------------------------------------------*/
|
|
20
20
|
|
|
21
21
|
import { injectable } from '@theia/core/shared/inversify';
|
|
22
|
+
import { ArrayUtils } from '@theia/core';
|
|
22
23
|
import * as monaco from '@theia/monaco-editor-core';
|
|
24
|
+
import { CancellationToken } from '@theia/monaco-editor-core/esm/vs/base/common/cancellation';
|
|
25
|
+
import { ILanguageFeaturesService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languageFeatures';
|
|
26
|
+
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
|
27
|
+
import { DebugEditor } from './debug-editor';
|
|
23
28
|
|
|
24
29
|
/**
|
|
25
30
|
* TODO: introduce a new request to LSP to look up an expression range: https://github.com/Microsoft/language-server-protocol/issues/462
|
|
26
31
|
*/
|
|
27
32
|
@injectable()
|
|
28
33
|
export class DebugExpressionProvider {
|
|
34
|
+
|
|
35
|
+
async getEvaluatableExpression(
|
|
36
|
+
editor: DebugEditor,
|
|
37
|
+
selection: monaco.IRange
|
|
38
|
+
): Promise<{ matchingExpression: string; range: monaco.IRange } | undefined> {
|
|
39
|
+
|
|
40
|
+
const pluginExpressionProvider = StandaloneServices.get(ILanguageFeaturesService).evaluatableExpressionProvider;
|
|
41
|
+
const textEditorModel = editor.document.textEditorModel;
|
|
42
|
+
|
|
43
|
+
if (pluginExpressionProvider && pluginExpressionProvider.has(textEditorModel)) {
|
|
44
|
+
const registeredProviders = pluginExpressionProvider.ordered(textEditorModel);
|
|
45
|
+
const position = new monaco.Position(selection.startLineNumber, selection.startColumn);
|
|
46
|
+
|
|
47
|
+
const promises = registeredProviders.map(support =>
|
|
48
|
+
Promise.resolve(support.provideEvaluatableExpression(textEditorModel, position, CancellationToken.None))
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const results = await Promise.all(promises).then(ArrayUtils.coalesce);
|
|
52
|
+
if (results.length > 0) {
|
|
53
|
+
const range = results[0].range;
|
|
54
|
+
const matchingExpression = results[0].expression || textEditorModel.getValueInRange(range);
|
|
55
|
+
return { matchingExpression, range };
|
|
56
|
+
}
|
|
57
|
+
} else { // use fallback if no provider was registered
|
|
58
|
+
const model = editor.getControl().getModel();
|
|
59
|
+
if (model) {
|
|
60
|
+
const lineContent = model.getLineContent(selection.startLineNumber);
|
|
61
|
+
const { start, end } = this.getExactExpressionStartAndEnd(lineContent, selection.startColumn, selection.endColumn);
|
|
62
|
+
const matchingExpression = lineContent.substring(start - 1, end - 1);
|
|
63
|
+
const range = new monaco.Range(
|
|
64
|
+
selection.startLineNumber,
|
|
65
|
+
start,
|
|
66
|
+
selection.startLineNumber,
|
|
67
|
+
end
|
|
68
|
+
);
|
|
69
|
+
return { matchingExpression, range };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
29
74
|
get(model: monaco.editor.IModel, selection: monaco.IRange): string {
|
|
30
75
|
const lineContent = model.getLineContent(selection.startLineNumber);
|
|
31
76
|
const { start, end } = this.getExactExpressionStartAndEnd(lineContent, selection.startColumn, selection.endColumn);
|
|
32
|
-
return lineContent.substring(start - 1, end);
|
|
77
|
+
return lineContent.substring(start - 1, end - 1);
|
|
33
78
|
}
|
|
34
79
|
protected getExactExpressionStartAndEnd(lineContent: string, looseStart: number, looseEnd: number): { start: number, end: number } {
|
|
35
80
|
let matchingExpression: string | undefined = undefined;
|
|
36
|
-
let startOffset =
|
|
81
|
+
let startOffset = 1;
|
|
37
82
|
|
|
38
83
|
// Some example supported expressions: myVar.prop, a.b.c.d, myVar?.prop, myVar->prop, MyClass::StaticProp, *myVar
|
|
39
84
|
// Match any character except a set of characters which often break interesting sub-expressions
|
|
@@ -72,7 +117,7 @@ export class DebugExpressionProvider {
|
|
|
72
117
|
}
|
|
73
118
|
|
|
74
119
|
return matchingExpression ?
|
|
75
|
-
{ start: startOffset, end: startOffset + matchingExpression.length
|
|
76
|
-
{ start:
|
|
120
|
+
{ start: startOffset, end: startOffset + matchingExpression.length } :
|
|
121
|
+
{ start: 1, end: 1 };
|
|
77
122
|
}
|
|
78
123
|
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import debounce = require('@theia/core/shared/lodash.debounce');
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { MenuPath } from '@theia/core';
|
|
20
20
|
import { Key } from '@theia/core/lib/browser';
|
|
21
21
|
import { SourceTreeWidget } from '@theia/core/lib/browser/source-tree';
|
|
22
22
|
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
|
@@ -25,10 +25,7 @@ import { Widget } from '@theia/core/shared/@lumino/widgets';
|
|
|
25
25
|
import { Container, inject, injectable, interfaces, postConstruct } from '@theia/core/shared/inversify';
|
|
26
26
|
import { URI as CodeUri } from '@theia/core/shared/vscode-uri';
|
|
27
27
|
import * as monaco from '@theia/monaco-editor-core';
|
|
28
|
-
import { CancellationTokenSource } from '@theia/monaco-editor-core/esm/vs/base/common/cancellation';
|
|
29
28
|
import { IEditorHoverOptions } from '@theia/monaco-editor-core/esm/vs/editor/common/config/editorOptions';
|
|
30
|
-
import { Position } from '@theia/monaco-editor-core/esm/vs/editor/common/core/position';
|
|
31
|
-
import { ILanguageFeaturesService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languageFeatures';
|
|
32
29
|
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
|
33
30
|
import { IConfigurationService } from '@theia/monaco-editor-core/esm/vs/platform/configuration/common/configuration';
|
|
34
31
|
import { DebugVariable } from '../console/debug-console-items';
|
|
@@ -58,7 +55,6 @@ export function createDebugHoverWidgetContainer(parent: interfaces.Container, ed
|
|
|
58
55
|
child.bind(DebugEditor).toConstantValue(editor);
|
|
59
56
|
child.bind(DebugHoverSource).toSelf();
|
|
60
57
|
child.unbind(SourceTreeWidget);
|
|
61
|
-
child.bind(DebugExpressionProvider).toSelf();
|
|
62
58
|
child.bind(DebugHoverWidget).toSelf();
|
|
63
59
|
return child;
|
|
64
60
|
}
|
|
@@ -167,7 +163,6 @@ export class DebugHoverWidget extends SourceTreeWidget implements monaco.editor.
|
|
|
167
163
|
}
|
|
168
164
|
|
|
169
165
|
protected async doShow(options: ShowDebugHoverOptions | undefined = this.options): Promise<void> {
|
|
170
|
-
const cancellationSource = new CancellationTokenSource();
|
|
171
166
|
|
|
172
167
|
if (!this.isEditorFrame()) {
|
|
173
168
|
this.hide();
|
|
@@ -185,56 +180,16 @@ export class DebugHoverWidget extends SourceTreeWidget implements monaco.editor.
|
|
|
185
180
|
}
|
|
186
181
|
|
|
187
182
|
this.options = options;
|
|
188
|
-
let matchingExpression: string | undefined;
|
|
189
183
|
|
|
190
|
-
const
|
|
191
|
-
const textEditorModel = this.editor.document.textEditorModel;
|
|
184
|
+
const result = await this.expressionProvider.getEvaluatableExpression(this.editor, options.selection);
|
|
192
185
|
|
|
193
|
-
if (
|
|
194
|
-
const registeredProviders = pluginExpressionProvider.ordered(textEditorModel);
|
|
195
|
-
const position = new Position(this.options!.selection.startLineNumber, this.options!.selection.startColumn);
|
|
196
|
-
|
|
197
|
-
const promises = registeredProviders.map(support =>
|
|
198
|
-
Promise.resolve(support.provideEvaluatableExpression(textEditorModel, position, cancellationSource.token))
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
const results = await Promise.all(promises).then(ArrayUtils.coalesce);
|
|
202
|
-
if (results.length > 0) {
|
|
203
|
-
matchingExpression = results[0].expression;
|
|
204
|
-
const range = results[0].range;
|
|
205
|
-
|
|
206
|
-
if (!matchingExpression) {
|
|
207
|
-
const lineContent = textEditorModel.getLineContent(position.lineNumber);
|
|
208
|
-
matchingExpression = lineContent.substring(range.startColumn - 1, range.endColumn - 1);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
} else { // use fallback if no provider was registered
|
|
212
|
-
const model = this.editor.getControl().getModel();
|
|
213
|
-
if (model) {
|
|
214
|
-
|
|
215
|
-
matchingExpression = this.expressionProvider.get(model, options.selection);
|
|
216
|
-
if (matchingExpression) {
|
|
217
|
-
const expressionLineContent = model.getLineContent(this.options.selection.startLineNumber);
|
|
218
|
-
const startColumn =
|
|
219
|
-
expressionLineContent.indexOf(
|
|
220
|
-
matchingExpression,
|
|
221
|
-
this.options.selection.startColumn - matchingExpression.length
|
|
222
|
-
) + 1;
|
|
223
|
-
const endColumn = startColumn + matchingExpression.length;
|
|
224
|
-
this.options.selection = new monaco.Range(
|
|
225
|
-
this.options.selection.startLineNumber,
|
|
226
|
-
startColumn,
|
|
227
|
-
this.options.selection.startLineNumber,
|
|
228
|
-
endColumn
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (!matchingExpression) {
|
|
186
|
+
if (!result?.matchingExpression) {
|
|
235
187
|
this.hide();
|
|
236
188
|
return;
|
|
237
189
|
}
|
|
190
|
+
|
|
191
|
+
this.options.selection = monaco.Range.lift(result.range);
|
|
192
|
+
|
|
238
193
|
const toFocus = new DisposableCollection();
|
|
239
194
|
if (this.options.focus === true) {
|
|
240
195
|
toFocus.push(this.model.onNodeRefreshed(() => {
|
|
@@ -242,7 +197,7 @@ export class DebugHoverWidget extends SourceTreeWidget implements monaco.editor.
|
|
|
242
197
|
this.activate();
|
|
243
198
|
}));
|
|
244
199
|
}
|
|
245
|
-
const expression = await this.hoverSource.evaluate(matchingExpression);
|
|
200
|
+
const expression = await this.hoverSource.evaluate(result.matchingExpression);
|
|
246
201
|
if (!expression) {
|
|
247
202
|
toFocus.dispose();
|
|
248
203
|
this.hide();
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 EclipseSource GmbH 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 { nls } from '@theia/core';
|
|
18
|
+
import * as React from '@theia/core/shared/react';
|
|
19
|
+
import { BreakpointManager } from '../breakpoint/breakpoint-manager';
|
|
20
|
+
import { DataBreakpoint } from '../breakpoint/breakpoint-marker';
|
|
21
|
+
import { DebugBreakpoint, DebugBreakpointDecoration, DebugBreakpointOptions } from './debug-breakpoint';
|
|
22
|
+
|
|
23
|
+
export class DebugDataBreakpoint extends DebugBreakpoint<DataBreakpoint> {
|
|
24
|
+
constructor(readonly origin: DataBreakpoint, options: DebugBreakpointOptions) {
|
|
25
|
+
super(BreakpointManager.DATA_URI, options);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setEnabled(enabled: boolean): void {
|
|
29
|
+
if (enabled !== this.origin.enabled) {
|
|
30
|
+
this.breakpoints.updateDataBreakpoint(this.origin.id, { enabled });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
protected override isEnabled(): boolean {
|
|
35
|
+
return super.isEnabled() && this.isSupported();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected isSupported(): boolean {
|
|
39
|
+
return Boolean(this.session?.capabilities.supportsDataBreakpoints);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
remove(): void {
|
|
43
|
+
this.breakpoints.removeDataBreakpoint(this.origin.id);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
protected doRender(): React.ReactNode {
|
|
47
|
+
return <span className="line-info theia-data-breakpoint" title={this.origin.info.description}>
|
|
48
|
+
<span className="name">{this.origin.info.description}</span>
|
|
49
|
+
<span className="theia-TreeNodeInfo theia-access-type" >{this.getAccessType()}</span>
|
|
50
|
+
</span>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
protected getAccessType(): string {
|
|
54
|
+
switch (this.origin.raw.accessType) {
|
|
55
|
+
case 'read': return 'Read';
|
|
56
|
+
case 'write': return 'Write';
|
|
57
|
+
default: return 'Access';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
protected getBreakpointDecoration(message?: string[]): DebugBreakpointDecoration {
|
|
62
|
+
if (!this.isSupported()) {
|
|
63
|
+
return {
|
|
64
|
+
className: 'codicon-debug-breakpoint-unsupported',
|
|
65
|
+
message: message ?? [nls.localize('theia/debug/data-breakpoint', 'Data Breakpoint')],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (this.origin.raw.condition || this.origin.raw.hitCondition) {
|
|
69
|
+
return {
|
|
70
|
+
className: 'codicon-debug-breakpoint-conditional',
|
|
71
|
+
message: message || [nls.localize('theia/debug/conditionalBreakpoint', 'Conditional Breakpoint')]
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
className: 'codicon-debug-breakpoint-data',
|
|
76
|
+
message: message || [nls.localize('theia/debug/data-breakpoint', 'Data Breakpoint')]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -39,15 +39,9 @@ export class DebugBreakpointsSource extends TreeSource {
|
|
|
39
39
|
for (const exceptionBreakpoint of this.breakpoints.getExceptionBreakpoints()) {
|
|
40
40
|
yield new DebugExceptionBreakpoint(exceptionBreakpoint, this.breakpoints);
|
|
41
41
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
yield instructionBreakpoint;
|
|
47
|
-
}
|
|
48
|
-
for (const breakpoint of this.model.breakpoints) {
|
|
49
|
-
yield breakpoint;
|
|
50
|
-
}
|
|
42
|
+
yield* this.model.dataBreakpoints;
|
|
43
|
+
yield* this.model.functionBreakpoints;
|
|
44
|
+
yield* this.model.instructionBreakpoints;
|
|
45
|
+
yield* this.model.breakpoints;
|
|
51
46
|
}
|
|
52
|
-
|
|
53
47
|
}
|
|
@@ -122,8 +122,12 @@ export class DebugStackFramesWidget extends SourceTreeWidget {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
protected override tapNode(node?: TreeNode): void {
|
|
125
|
-
if (TreeElementNode.is(node)
|
|
126
|
-
node.element
|
|
125
|
+
if (TreeElementNode.is(node)) {
|
|
126
|
+
if (node.element instanceof LoadMoreStackFrames) {
|
|
127
|
+
node.element.open();
|
|
128
|
+
} else if (node.element instanceof DebugStackFrame) {
|
|
129
|
+
node.element.open({ preview: true });
|
|
130
|
+
}
|
|
127
131
|
}
|
|
128
132
|
super.tapNode(node);
|
|
129
133
|
}
|
|
@@ -15,11 +15,16 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
17
|
import { injectable, inject, postConstruct, interfaces, Container } from '@theia/core/shared/inversify';
|
|
18
|
-
import { MenuPath } from '@theia/core/lib/common';
|
|
19
|
-
import { SourceTreeWidget } from '@theia/core/lib/browser/source-tree';
|
|
18
|
+
import { MenuPath, Disposable, CommandRegistry, MenuModelRegistry, DisposableCollection, Command } from '@theia/core/lib/common';
|
|
19
|
+
import { SourceTreeWidget, TreeElementNode } from '@theia/core/lib/browser/source-tree';
|
|
20
20
|
import { DebugVariablesSource } from './debug-variables-source';
|
|
21
21
|
import { DebugViewModel } from './debug-view-model';
|
|
22
22
|
import { nls } from '@theia/core/lib/common/nls';
|
|
23
|
+
import { MouseEvent } from '@theia/core/shared/react';
|
|
24
|
+
import { SelectableTreeNode, TreeNode, TreeSelection } from '@theia/core/lib/browser';
|
|
25
|
+
import { DebugVariable } from '../console/debug-console-items';
|
|
26
|
+
import { BreakpointManager } from '../breakpoint/breakpoint-manager';
|
|
27
|
+
import { DataBreakpoint, DataBreakpointSource, DataBreakpointSourceType } from '../breakpoint/breakpoint-marker';
|
|
23
28
|
|
|
24
29
|
@injectable()
|
|
25
30
|
export class DebugVariablesWidget extends SourceTreeWidget {
|
|
@@ -27,6 +32,7 @@ export class DebugVariablesWidget extends SourceTreeWidget {
|
|
|
27
32
|
static CONTEXT_MENU: MenuPath = ['debug-variables-context-menu'];
|
|
28
33
|
static EDIT_MENU: MenuPath = [...DebugVariablesWidget.CONTEXT_MENU, 'a_edit'];
|
|
29
34
|
static WATCH_MENU: MenuPath = [...DebugVariablesWidget.CONTEXT_MENU, 'b_watch'];
|
|
35
|
+
static DATA_BREAKPOINT_MENU: MenuPath = [...DebugVariablesWidget.CONTEXT_MENU, 'c_data_breakpoints'];
|
|
30
36
|
static FACTORY_ID = 'debug:variables';
|
|
31
37
|
static override createContainer(parent: interfaces.Container): Container {
|
|
32
38
|
const child = SourceTreeWidget.createContainer(parent, {
|
|
@@ -49,6 +55,15 @@ export class DebugVariablesWidget extends SourceTreeWidget {
|
|
|
49
55
|
@inject(DebugVariablesSource)
|
|
50
56
|
protected readonly variables: DebugVariablesSource;
|
|
51
57
|
|
|
58
|
+
@inject(CommandRegistry)
|
|
59
|
+
protected readonly commandRegistry: CommandRegistry;
|
|
60
|
+
|
|
61
|
+
@inject(MenuModelRegistry)
|
|
62
|
+
protected readonly menuRegistry: MenuModelRegistry;
|
|
63
|
+
|
|
64
|
+
@inject(BreakpointManager)
|
|
65
|
+
protected readonly breakpointManager: BreakpointManager;
|
|
66
|
+
|
|
52
67
|
@postConstruct()
|
|
53
68
|
protected override init(): void {
|
|
54
69
|
super.init();
|
|
@@ -58,4 +73,86 @@ export class DebugVariablesWidget extends SourceTreeWidget {
|
|
|
58
73
|
this.source = this.variables;
|
|
59
74
|
}
|
|
60
75
|
|
|
76
|
+
protected override handleContextMenuEvent(node: TreeNode | undefined, event: MouseEvent<HTMLElement>): void {
|
|
77
|
+
this.doHandleContextMenuEvent(node, event);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
protected async doHandleContextMenuEvent(node: TreeNode | undefined, event: MouseEvent<HTMLElement>): Promise<void> {
|
|
81
|
+
event.stopPropagation();
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
if (!SelectableTreeNode.is(node) || !TreeElementNode.is(node)) { return; }
|
|
84
|
+
// Keep the selection for the context menu, if the widget support multi-selection and the right click happens on an already selected node.
|
|
85
|
+
if (!this.props.multiSelect || !node.selected) {
|
|
86
|
+
const type = !!this.props.multiSelect && this.hasCtrlCmdMask(event) ? TreeSelection.SelectionType.TOGGLE : TreeSelection.SelectionType.DEFAULT;
|
|
87
|
+
this.model.addSelection({ node, type });
|
|
88
|
+
}
|
|
89
|
+
this.focusService.setFocus(node);
|
|
90
|
+
const contextMenuPath = this.props.contextMenuPath;
|
|
91
|
+
if (contextMenuPath) {
|
|
92
|
+
const { x, y } = event.nativeEvent;
|
|
93
|
+
const args = this.toContextMenuArgs(node);
|
|
94
|
+
const target = event.currentTarget;
|
|
95
|
+
const toDisposeOnHide = await this.getVariableCommands(node);
|
|
96
|
+
setTimeout(() => this.contextMenuRenderer.render({
|
|
97
|
+
menuPath: contextMenuPath,
|
|
98
|
+
context: target,
|
|
99
|
+
anchor: { x, y },
|
|
100
|
+
args,
|
|
101
|
+
onHide: () => toDisposeOnHide.dispose()
|
|
102
|
+
}), 10);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
protected async getVariableCommands(node: TreeElementNode): Promise<Disposable> {
|
|
107
|
+
const selectedElement = node.element;
|
|
108
|
+
const { viewModel: { currentSession } } = this;
|
|
109
|
+
if (!currentSession?.capabilities.supportsDataBreakpoints || !(selectedElement instanceof DebugVariable)) {
|
|
110
|
+
return Disposable.NULL;
|
|
111
|
+
}
|
|
112
|
+
const { name, parent: { reference } } = selectedElement;
|
|
113
|
+
const dataBreakpointInfo = (await currentSession.sendRequest('dataBreakpointInfo', { name, variablesReference: reference })).body;
|
|
114
|
+
// eslint-disable-next-line no-null/no-null
|
|
115
|
+
if (dataBreakpointInfo.dataId === null) {
|
|
116
|
+
return Disposable.NULL;
|
|
117
|
+
}
|
|
118
|
+
const source: DataBreakpointSource = { type: DataBreakpointSourceType.Variable, variable: name };
|
|
119
|
+
return new DisposableCollection(
|
|
120
|
+
this.commandRegistry.registerCommand(Command.toDefaultLocalizedCommand({
|
|
121
|
+
id: `break-on-access:${currentSession.id}:${name}`,
|
|
122
|
+
label: 'Break on Value Access'
|
|
123
|
+
}), {
|
|
124
|
+
execute: () => this.breakpointManager.addDataBreakpoint(DataBreakpoint.create(
|
|
125
|
+
{ accessType: 'readWrite', dataId: dataBreakpointInfo.dataId! },
|
|
126
|
+
dataBreakpointInfo,
|
|
127
|
+
source
|
|
128
|
+
)),
|
|
129
|
+
isEnabled: () => !!dataBreakpointInfo.accessTypes?.includes('readWrite'),
|
|
130
|
+
}),
|
|
131
|
+
this.menuRegistry.registerMenuAction(DebugVariablesWidget.DATA_BREAKPOINT_MENU, { commandId: `break-on-access:${currentSession.id}:${name}`, order: 'c' }),
|
|
132
|
+
this.commandRegistry.registerCommand(Command.toDefaultLocalizedCommand({
|
|
133
|
+
id: `break-on-read:${currentSession.id}:${name}`,
|
|
134
|
+
label: 'Break on Value Read'
|
|
135
|
+
}), {
|
|
136
|
+
execute: () => this.breakpointManager.addDataBreakpoint(DataBreakpoint.create(
|
|
137
|
+
{ accessType: 'read', dataId: dataBreakpointInfo.dataId! },
|
|
138
|
+
dataBreakpointInfo,
|
|
139
|
+
source
|
|
140
|
+
)),
|
|
141
|
+
isEnabled: () => !!dataBreakpointInfo.accessTypes?.includes('read'),
|
|
142
|
+
}),
|
|
143
|
+
this.menuRegistry.registerMenuAction(DebugVariablesWidget.DATA_BREAKPOINT_MENU, { commandId: `break-on-read:${currentSession.id}:${name}`, order: 'a' }),
|
|
144
|
+
this.commandRegistry.registerCommand(Command.toDefaultLocalizedCommand({
|
|
145
|
+
id: `break-on-write:${currentSession.id}:${name}`,
|
|
146
|
+
label: 'Break on Value Change'
|
|
147
|
+
}), {
|
|
148
|
+
execute: () => this.breakpointManager.addDataBreakpoint(DataBreakpoint.create(
|
|
149
|
+
{ accessType: 'write', dataId: dataBreakpointInfo.dataId! },
|
|
150
|
+
dataBreakpointInfo,
|
|
151
|
+
source
|
|
152
|
+
)),
|
|
153
|
+
isEnabled: () => !!dataBreakpointInfo.accessTypes?.includes('write'),
|
|
154
|
+
}),
|
|
155
|
+
this.menuRegistry.registerMenuAction(DebugVariablesWidget.DATA_BREAKPOINT_MENU, { commandId: `break-on-write:${currentSession.id}:${name}`, order: 'b' }),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
61
158
|
}
|
|
@@ -28,6 +28,7 @@ import { DebugWatchManager } from '../debug-watch-manager';
|
|
|
28
28
|
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
|
+
import { DebugDataBreakpoint } from '../model/debug-data-breakpoint';
|
|
31
32
|
|
|
32
33
|
@injectable()
|
|
33
34
|
export class DebugViewModel implements Disposable {
|
|
@@ -137,6 +138,10 @@ export class DebugViewModel implements Disposable {
|
|
|
137
138
|
return this.manager.getInstructionBreakpoints(this.currentSession);
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
get dataBreakpoints(): DebugDataBreakpoint[] {
|
|
142
|
+
return this.manager.getDataBreakpoints(this.currentSession);
|
|
143
|
+
}
|
|
144
|
+
|
|
140
145
|
async start(options: Partial<Pick<DebugSessionOptionsBase, 'startedByUser'>> = {}): Promise<void> {
|
|
141
146
|
const { session } = this;
|
|
142
147
|
if (!session) {
|