@theia/debug 1.77.0-next.25 → 1.77.0-next.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/console/debug-console-contribution.d.ts +23 -1
- package/lib/browser/console/debug-console-contribution.d.ts.map +1 -1
- package/lib/browser/console/debug-console-contribution.js +47 -14
- package/lib/browser/console/debug-console-contribution.js.map +1 -1
- package/lib/browser/console/debug-console-contribution.spec.d.ts +2 -0
- package/lib/browser/console/debug-console-contribution.spec.d.ts.map +1 -0
- package/lib/browser/console/debug-console-contribution.spec.js +142 -0
- package/lib/browser/console/debug-console-contribution.spec.js.map +1 -0
- package/lib/browser/console/debug-console-items.d.ts.map +1 -1
- package/lib/browser/console/debug-console-items.js +6 -1
- package/lib/browser/console/debug-console-items.js.map +1 -1
- package/lib/browser/console/debug-console-session.d.ts +48 -3
- package/lib/browser/console/debug-console-session.d.ts.map +1 -1
- package/lib/browser/console/debug-console-session.js +90 -17
- package/lib/browser/console/debug-console-session.js.map +1 -1
- package/lib/browser/console/debug-console-session.spec.d.ts +2 -0
- package/lib/browser/console/debug-console-session.spec.d.ts.map +1 -0
- package/lib/browser/console/debug-console-session.spec.js +187 -0
- package/lib/browser/console/debug-console-session.spec.js.map +1 -0
- package/lib/browser/debug-session-connection.d.ts +6 -0
- package/lib/browser/debug-session-connection.d.ts.map +1 -1
- package/lib/browser/debug-session-connection.js +8 -0
- package/lib/browser/debug-session-connection.js.map +1 -1
- package/lib/browser/debug-session-manager.d.ts.map +1 -1
- package/lib/browser/debug-session-manager.js +5 -2
- package/lib/browser/debug-session-manager.js.map +1 -1
- package/lib/browser/debug-session.d.ts.map +1 -1
- package/lib/browser/debug-session.js +6 -0
- package/lib/browser/debug-session.js.map +1 -1
- package/lib/browser/view/debug-variables-widget.d.ts.map +1 -1
- package/lib/browser/view/debug-variables-widget.js +6 -0
- package/lib/browser/view/debug-variables-widget.js.map +1 -1
- package/lib/common/debug-preferences.d.ts +1 -0
- package/lib/common/debug-preferences.d.ts.map +1 -1
- package/lib/common/debug-preferences.js +6 -0
- package/lib/common/debug-preferences.js.map +1 -1
- package/package.json +15 -15
- package/src/browser/console/debug-console-contribution.spec.ts +183 -0
- package/src/browser/console/debug-console-contribution.tsx +53 -14
- package/src/browser/console/debug-console-items.tsx +6 -1
- package/src/browser/console/debug-console-session.spec.ts +246 -0
- package/src/browser/console/debug-console-session.ts +102 -19
- package/src/browser/debug-session-connection.ts +9 -0
- package/src/browser/debug-session-manager.ts +5 -2
- package/src/browser/debug-session.tsx +6 -0
- package/src/browser/view/debug-variables-widget.ts +6 -0
- package/src/common/debug-preferences.ts +7 -0
|
@@ -20,10 +20,12 @@ import { ConsoleSession, ConsoleItem } from '@theia/console/lib/browser/console-
|
|
|
20
20
|
import { AnsiConsoleItem } from '@theia/console/lib/browser/ansi-console-item';
|
|
21
21
|
import { DebugSession } from '../debug-session';
|
|
22
22
|
import URI from '@theia/core/lib/common/uri';
|
|
23
|
+
import { generateUuid } from '@theia/core/lib/common/uuid';
|
|
23
24
|
import { ExpressionContainer, ExpressionItem } from './debug-console-items';
|
|
24
25
|
import { Severity } from '@theia/core/lib/common/severity';
|
|
25
26
|
import { inject, injectable, postConstruct, named } from '@theia/core/shared/inversify';
|
|
26
27
|
import { DebugSessionManager } from '../debug-session-manager';
|
|
28
|
+
import { DebugPreferences } from '../../common/debug-preferences';
|
|
27
29
|
import * as monaco from '@theia/monaco-editor-core';
|
|
28
30
|
import { LanguageSelector } from '@theia/monaco-editor-core/esm/vs/editor/common/languageSelector';
|
|
29
31
|
import { Disposable, ILogger } from '@theia/core';
|
|
@@ -42,24 +44,61 @@ export class DebugConsoleSession extends ConsoleSession {
|
|
|
42
44
|
@inject(ILogger) @named('debug:DebugConsoleSession')
|
|
43
45
|
protected readonly logger: ILogger;
|
|
44
46
|
|
|
47
|
+
@inject(DebugPreferences)
|
|
48
|
+
protected readonly preferences: DebugPreferences;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The console has an identity of its own, independent of the sessions that run in it: it outlives the session it
|
|
52
|
+
* was created for and is reused by a later one.
|
|
53
|
+
*/
|
|
54
|
+
override id = generateUuid();
|
|
55
|
+
|
|
45
56
|
protected items: ConsoleItem[] = [];
|
|
46
57
|
|
|
47
58
|
protected _terminated = false;
|
|
48
59
|
|
|
49
|
-
protected _debugSession: DebugSession;
|
|
60
|
+
protected _debugSession: DebugSession | undefined;
|
|
61
|
+
|
|
62
|
+
protected _label = '';
|
|
63
|
+
|
|
64
|
+
protected _configurationName: string | undefined;
|
|
65
|
+
|
|
66
|
+
protected _workspaceFolderUri: string | undefined;
|
|
50
67
|
|
|
51
68
|
// content buffer for [append](#append) method
|
|
52
69
|
protected uncompletedItemContent: string | undefined;
|
|
53
70
|
|
|
54
71
|
protected readonly completionKinds = new Map<DebugProtocol.CompletionItemType | undefined, monaco.languages.CompletionItemKind>();
|
|
55
72
|
|
|
56
|
-
|
|
73
|
+
/**
|
|
74
|
+
* The debug session this console currently belongs to, or `undefined` once that session has terminated.
|
|
75
|
+
*
|
|
76
|
+
* The reference is dropped on termination so that the console does not keep the whole session graph alive
|
|
77
|
+
* while it preserves the output of that session.
|
|
78
|
+
*/
|
|
79
|
+
get debugSession(): DebugSession | undefined {
|
|
57
80
|
return this._debugSession;
|
|
58
81
|
}
|
|
59
82
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
83
|
+
/**
|
|
84
|
+
* The label of the session this console belongs to, retained after that session has terminated.
|
|
85
|
+
*/
|
|
86
|
+
get label(): string {
|
|
87
|
+
return this._label;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The name of the configuration this console was started for, used to match a restarted session to its console.
|
|
92
|
+
*/
|
|
93
|
+
get configurationName(): string | undefined {
|
|
94
|
+
return this._configurationName;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The workspace folder of the configuration this console was started for, if any.
|
|
99
|
+
*/
|
|
100
|
+
get workspaceFolderUri(): string | undefined {
|
|
101
|
+
return this._workspaceFolderUri;
|
|
63
102
|
}
|
|
64
103
|
|
|
65
104
|
get terminated(): boolean {
|
|
@@ -69,10 +108,41 @@ export class DebugConsoleSession extends ConsoleSession {
|
|
|
69
108
|
markTerminated(): void {
|
|
70
109
|
if (!this._terminated) {
|
|
71
110
|
this._terminated = true;
|
|
111
|
+
// Release the session: from here on the console only holds the output it has already collected.
|
|
112
|
+
this._debugSession = undefined;
|
|
72
113
|
this.fireDidChange();
|
|
73
114
|
}
|
|
74
115
|
}
|
|
75
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Whether the given session can take over this console: its session must have terminated and been started from the
|
|
119
|
+
* same configuration, which in a multi-root workspace includes the folder the configuration comes from.
|
|
120
|
+
*/
|
|
121
|
+
isReusableBy(session: DebugSession): boolean {
|
|
122
|
+
return this._terminated
|
|
123
|
+
&& this._configurationName === session.configuration.name
|
|
124
|
+
&& this._workspaceFolderUri === session.options.workspaceFolderUri;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Runs the given session in this console, whether it is the session the console was created for or a later one
|
|
129
|
+
* reusing it. Counterpart of {@link markTerminated}.
|
|
130
|
+
*
|
|
131
|
+
* The output of any previous session is dropped: it belongs to that run, and keeping it would merge two runs into
|
|
132
|
+
* one view with no boundary between them. The filter and severity stay, since they are the view the user has set
|
|
133
|
+
* up for this configuration.
|
|
134
|
+
*/
|
|
135
|
+
startFor(session: DebugSession): void {
|
|
136
|
+
this.items = [];
|
|
137
|
+
this.uncompletedItemContent = undefined;
|
|
138
|
+
this._debugSession = session;
|
|
139
|
+
this._label = session.label;
|
|
140
|
+
this._configurationName = session.configuration.name;
|
|
141
|
+
this._workspaceFolderUri = session.options.workspaceFolderUri;
|
|
142
|
+
this._terminated = false;
|
|
143
|
+
this.fireDidChange();
|
|
144
|
+
}
|
|
145
|
+
|
|
76
146
|
@postConstruct()
|
|
77
147
|
init(): void {
|
|
78
148
|
this.completionKinds.set('method', monaco.languages.CompletionItemKind.Method);
|
|
@@ -175,16 +245,17 @@ export class DebugConsoleSession extends ConsoleSession {
|
|
|
175
245
|
}
|
|
176
246
|
|
|
177
247
|
protected findCurrentSession(): DebugSession | undefined {
|
|
248
|
+
const debugSession = this._debugSession;
|
|
178
249
|
const currentSession = this.sessionManager.currentSession;
|
|
179
|
-
if (!currentSession) {
|
|
250
|
+
if (!debugSession || !currentSession) {
|
|
180
251
|
return undefined;
|
|
181
252
|
}
|
|
182
|
-
if (currentSession.id ===
|
|
253
|
+
if (currentSession.id === debugSession.id) {
|
|
183
254
|
// perfect match
|
|
184
|
-
return
|
|
255
|
+
return debugSession;
|
|
185
256
|
}
|
|
186
257
|
const parentSession = currentSession.findConsoleParent();
|
|
187
|
-
if (parentSession?.id ===
|
|
258
|
+
if (parentSession?.id === debugSession.id) {
|
|
188
259
|
// child of our session
|
|
189
260
|
return currentSession;
|
|
190
261
|
}
|
|
@@ -215,7 +286,7 @@ export class DebugConsoleSession extends ConsoleSession {
|
|
|
215
286
|
|
|
216
287
|
async execute(value: string): Promise<void> {
|
|
217
288
|
const expression = new ExpressionItem(value, () => this.findCurrentSession());
|
|
218
|
-
this.
|
|
289
|
+
this.appendItems([expression]);
|
|
219
290
|
await expression.evaluate();
|
|
220
291
|
this.fireDidChange();
|
|
221
292
|
}
|
|
@@ -238,15 +309,28 @@ export class DebugConsoleSession extends ConsoleSession {
|
|
|
238
309
|
this.uncompletedItemContent = value;
|
|
239
310
|
}
|
|
240
311
|
|
|
241
|
-
this.
|
|
312
|
+
this.appendItems([new AnsiConsoleItem(this.uncompletedItemContent, Severity.Info)]);
|
|
242
313
|
this.fireDidChange();
|
|
243
314
|
}
|
|
244
315
|
|
|
245
316
|
appendLine(value: string): void {
|
|
246
|
-
this.
|
|
317
|
+
this.appendItems([new AnsiConsoleItem(value, Severity.Info)]);
|
|
247
318
|
this.fireDidChange();
|
|
248
319
|
}
|
|
249
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Appends items, dropping the oldest ones so that the console stays within `debug.console.maximumLines`.
|
|
323
|
+
*/
|
|
324
|
+
protected appendItems(items: ConsoleItem[]): void {
|
|
325
|
+
for (const item of items) {
|
|
326
|
+
this.items.push(item);
|
|
327
|
+
}
|
|
328
|
+
const maximumLines = this.preferences['debug.console.maximumLines'];
|
|
329
|
+
if (maximumLines > 0 && this.items.length > maximumLines) {
|
|
330
|
+
this.items.splice(0, this.items.length - maximumLines);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
250
334
|
async logOutput(session: DebugSession, event: DebugProtocol.OutputEvent): Promise<void> {
|
|
251
335
|
const body = event.body;
|
|
252
336
|
const { category, variablesReference } = body;
|
|
@@ -256,14 +340,13 @@ export class DebugConsoleSession extends ConsoleSession {
|
|
|
256
340
|
}
|
|
257
341
|
const severity = category === 'stderr' ? Severity.Error : event.body.category === 'console' ? Severity.Warning : Severity.Info;
|
|
258
342
|
if (variablesReference) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
343
|
+
// Resolve the session by id rather than capturing it, so that the items do not keep it alive once it is gone.
|
|
344
|
+
const { id } = session;
|
|
345
|
+
const sessionProvider = () => this.sessionManager.getSession(id);
|
|
346
|
+
const items = await new ExpressionContainer({ session: sessionProvider, variablesReference }).getElements();
|
|
347
|
+
this.appendItems(Array.from(items, item => Object.assign(item, { severity })));
|
|
263
348
|
} else if (typeof body.output === 'string') {
|
|
264
|
-
|
|
265
|
-
this.items.push(new AnsiConsoleItem(line, severity));
|
|
266
|
-
}
|
|
349
|
+
this.appendItems(body.output.split('\n').map(line => new AnsiConsoleItem(line, severity)));
|
|
267
350
|
}
|
|
268
351
|
this.fireDidChange();
|
|
269
352
|
}
|
|
@@ -151,6 +151,15 @@ export class DebugSessionConnection implements Disposable {
|
|
|
151
151
|
return this.toDispose.disposed;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Whether the underlying channel has been closed, so that no further request can be sent.
|
|
156
|
+
*
|
|
157
|
+
* This becomes `true` as soon as the channel closes, which is before the connection itself is disposed.
|
|
158
|
+
*/
|
|
159
|
+
get closed(): boolean {
|
|
160
|
+
return this.isClosed;
|
|
161
|
+
}
|
|
162
|
+
|
|
154
163
|
protected checkDisposed(): void {
|
|
155
164
|
if (this.disposed) {
|
|
156
165
|
throw new Error('the debug session connection is disposed, id: ' + this.sessionId);
|
|
@@ -568,9 +568,12 @@ export class DebugSessionManager {
|
|
|
568
568
|
this.disposeOnCurrentSessionChanged.push(current.onDidResolveLazyVariable(variable => this.onDidResolveLazyVariableEmitter.fire({ session: current, variable })));
|
|
569
569
|
this.disposeOnCurrentSessionChanged.push(current.onDidFocusStackFrame(frame => this.onDidFocusStackFrameEmitter.fire(frame)));
|
|
570
570
|
this.disposeOnCurrentSessionChanged.push(current.onDidFocusThread(thread => this.onDidFocusThreadEmitter.fire(thread)));
|
|
571
|
-
const { currentThread } = current;
|
|
572
|
-
this.onDidFocusThreadEmitter.fire(currentThread);
|
|
573
571
|
}
|
|
572
|
+
// Announce what is focused now, also when nothing is: the forwarding above is torn down with the previous
|
|
573
|
+
// session, so without this the last thread and frame of a session that has ended stay the focused ones.
|
|
574
|
+
const { currentThread, currentFrame } = this;
|
|
575
|
+
this.onDidFocusThreadEmitter.fire(currentThread);
|
|
576
|
+
this.onDidFocusStackFrameEmitter.fire(currentFrame);
|
|
574
577
|
this.open();
|
|
575
578
|
this.fireDidChange(current);
|
|
576
579
|
}
|
|
@@ -733,6 +733,12 @@ export class DebugSession implements CompositeTreeElement {
|
|
|
733
733
|
const { uri, sourceModified } = options;
|
|
734
734
|
await this.deferredOnDidConfigureCapabilities.promise;
|
|
735
735
|
for (const affectedUri of this.getAffectedUris(uri)) {
|
|
736
|
+
if (this.connection.closed) {
|
|
737
|
+
// The session ended while the breakpoints were being sent, which happens because ending one session
|
|
738
|
+
// updates the breakpoints of all of them. There is nothing left to send them to, and reporting them
|
|
739
|
+
// as unverified would outlive the session they failed for.
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
736
742
|
if (affectedUri.toString() === BreakpointManager.EXCEPTION_URI.toString()) {
|
|
737
743
|
await this.sendExceptionBreakpoints();
|
|
738
744
|
} else if (affectedUri.toString() === BreakpointManager.FUNCTION_URI.toString()) {
|
|
@@ -118,6 +118,12 @@ export class DebugVariablesWidget extends SourceTreeWidget {
|
|
|
118
118
|
|
|
119
119
|
protected handleDidDestroyDebugSession(session: DebugSession): void {
|
|
120
120
|
this.statePerSession.delete(session.id);
|
|
121
|
+
if (this.stackFrame?.session.id === session.id) {
|
|
122
|
+
// Release the frame, which holds its session: this widget outlives every session, and no stack frame is
|
|
123
|
+
// focused when the session it belongs to is gone. `onDidFocusStackFrame` does not announce that, as the
|
|
124
|
+
// session manager stops forwarding the event as soon as the session is no longer the current one.
|
|
125
|
+
this.stackFrame = undefined;
|
|
126
|
+
}
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
protected override handleContextMenuEvent(node: TreeNode | undefined, event: MouseEvent<HTMLElement>): void {
|
|
@@ -35,6 +35,12 @@ export const debugPreferencesSchema: PreferenceSchema = {
|
|
|
35
35
|
default: 'openOnFirstSessionStart',
|
|
36
36
|
description: nls.localizeByDefault('Controls when the internal Debug Console should open.')
|
|
37
37
|
},
|
|
38
|
+
'debug.console.maximumLines': {
|
|
39
|
+
type: 'number',
|
|
40
|
+
default: 10000,
|
|
41
|
+
minimum: 1,
|
|
42
|
+
description: nls.localizeByDefault('Controls the maximum number of lines in the Debug Console.')
|
|
43
|
+
},
|
|
38
44
|
'debug.inlineValues': {
|
|
39
45
|
type: 'boolean',
|
|
40
46
|
default: false,
|
|
@@ -92,6 +98,7 @@ export class DebugConfiguration {
|
|
|
92
98
|
'debug.trace': boolean;
|
|
93
99
|
'debug.openDebug': 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart' | 'openOnDebugBreak';
|
|
94
100
|
'debug.internalConsoleOptions': 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart';
|
|
101
|
+
'debug.console.maximumLines': number;
|
|
95
102
|
'debug.inlineValues': boolean;
|
|
96
103
|
'debug.showInStatusBar': 'never' | 'always' | 'onFirstSessionStart';
|
|
97
104
|
'debug.confirmOnExit': 'never' | 'always';
|