Skip to content

Commit 454fad2

Browse files
committed
debug: debug console group support
fixes #34981
1 parent cb9ba64 commit 454fad2

File tree

5 files changed

+176
-7
lines changed

5 files changed

+176
-7
lines changed

src/vs/workbench/contrib/debug/browser/debugSession.ts

+11
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,17 @@ export class DebugSession implements IDebugSession {
832832
column: event.body.column ? event.body.column : 1,
833833
source: this.getSource(event.body.source)
834834
} : undefined;
835+
836+
if (event.body.group === 'start' || event.body.group === 'startCollapsed') {
837+
const expanded = event.body.group === 'start';
838+
this.repl.startGroup(event.body.output || '', expanded, source);
839+
return;
840+
}
841+
if (event.body.group === 'end') {
842+
this.repl.endGroup();
843+
// Do not return, the end event can have additional output in it
844+
}
845+
835846
if (event.body.variablesReference) {
836847
const container = new ExpressionContainer(this, undefined, event.body.variablesReference, generateUuid());
837848
outpuPromises.push(container.getChildren().then(async children => {

src/vs/workbench/contrib/debug/browser/repl.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,13 @@ import { RunOnceScheduler } from 'vs/base/common/async';
5151
import { FuzzyScore } from 'vs/base/common/filters';
5252
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
5353
import { PANEL_BACKGROUND } from 'vs/workbench/common/theme';
54-
import { ReplDelegate, ReplVariablesRenderer, ReplSimpleElementsRenderer, ReplEvaluationInputsRenderer, ReplEvaluationResultsRenderer, ReplRawObjectsRenderer, ReplDataSource, ReplAccessibilityProvider } from 'vs/workbench/contrib/debug/browser/replViewer';
54+
import { ReplDelegate, ReplVariablesRenderer, ReplSimpleElementsRenderer, ReplEvaluationInputsRenderer, ReplEvaluationResultsRenderer, ReplRawObjectsRenderer, ReplDataSource, ReplAccessibilityProvider, ReplGroupRenderer } from 'vs/workbench/contrib/debug/browser/replViewer';
5555
import { localize } from 'vs/nls';
5656
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
5757
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
5858
import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views';
5959
import { IOpenerService } from 'vs/platform/opener/common/opener';
60+
import { ReplGroup } from 'vs/workbench/contrib/debug/common/replModel';
6061

6162
const $ = dom.$;
6263

@@ -425,6 +426,16 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
425426

426427
const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight;
427428
await this.tree.updateChildren();
429+
430+
const session = this.tree.getInput();
431+
if (session) {
432+
const replElements = session.getReplElements();
433+
const lastElement = replElements.length ? replElements[replElements.length - 1] : undefined;
434+
if (lastElement instanceof ReplGroup && lastElement.autoExpand) {
435+
await this.tree.expand(lastElement);
436+
}
437+
}
438+
428439
if (lastElementVisible) {
429440
// Only scroll if we were scrolled all the way down before tree refreshed #10486
430441
revealLastElement(this.tree);
@@ -454,6 +465,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
454465
this.instantiationService.createInstance(ReplVariablesRenderer, linkDetector),
455466
this.instantiationService.createInstance(ReplSimpleElementsRenderer, linkDetector),
456467
new ReplEvaluationInputsRenderer(),
468+
new ReplGroupRenderer(),
457469
new ReplEvaluationResultsRenderer(linkDetector),
458470
new ReplRawObjectsRenderer(linkDetector),
459471
],

src/vs/workbench/contrib/debug/browser/replViewer.ts

+38-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import severity from 'vs/base/common/severity';
77
import * as dom from 'vs/base/browser/dom';
88
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
99
import { Variable } from 'vs/workbench/contrib/debug/common/debugModel';
10-
import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel';
10+
import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplEvaluationResult, ReplGroup } from 'vs/workbench/contrib/debug/common/replModel';
1111
import { CachedListVirtualDelegate } from 'vs/base/browser/ui/list/list';
1212
import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
1313
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -30,6 +30,10 @@ interface IReplEvaluationInputTemplateData {
3030
label: HighlightedLabel;
3131
}
3232

33+
interface IReplGroupTemplateData {
34+
label: HighlightedLabel;
35+
}
36+
3337
interface IReplEvaluationResultTemplateData {
3438
value: HTMLElement;
3539
annotation: HTMLElement;
@@ -76,6 +80,29 @@ export class ReplEvaluationInputsRenderer implements ITreeRenderer<ReplEvaluatio
7680
}
7781
}
7882

83+
export class ReplGroupRenderer implements ITreeRenderer<ReplGroup, FuzzyScore, IReplGroupTemplateData> {
84+
static readonly ID = 'replGroup';
85+
86+
get templateId(): string {
87+
return ReplGroupRenderer.ID;
88+
}
89+
90+
renderTemplate(container: HTMLElement): IReplEvaluationInputTemplateData {
91+
const input = dom.append(container, $('.expression'));
92+
const label = new HighlightedLabel(input, false);
93+
return { label };
94+
}
95+
96+
renderElement(element: ITreeNode<ReplGroup, FuzzyScore>, _index: number, templateData: IReplGroupTemplateData): void {
97+
const replGroup = element.element;
98+
templateData.label.set(replGroup.name, createMatches(element.filterData));
99+
}
100+
101+
disposeTemplate(_templateData: IReplEvaluationInputTemplateData): void {
102+
// noop
103+
}
104+
}
105+
79106
export class ReplEvaluationResultsRenderer implements ITreeRenderer<ReplEvaluationResult, FuzzyScore, IReplEvaluationResultTemplateData> {
80107
static readonly ID = 'replEvaluationResult';
81108

@@ -296,6 +323,9 @@ export class ReplDelegate extends CachedListVirtualDelegate<IReplElement> {
296323
// Variable with no name is a top level variable which should be rendered like a repl element #17404
297324
return ReplSimpleElementsRenderer.ID;
298325
}
326+
if (element instanceof ReplGroup) {
327+
return ReplGroupRenderer.ID;
328+
}
299329

300330
return ReplRawObjectsRenderer.ID;
301331
}
@@ -317,7 +347,7 @@ export class ReplDataSource implements IAsyncDataSource<IDebugSession, IReplElem
317347
return true;
318348
}
319349

320-
return !!(<IExpressionContainer>element).hasChildren;
350+
return !!(<IExpressionContainer | ReplGroup>element).hasChildren;
321351
}
322352

323353
getChildren(element: IReplElement | IDebugSession): Promise<IReplElement[]> {
@@ -327,6 +357,9 @@ export class ReplDataSource implements IAsyncDataSource<IDebugSession, IReplElem
327357
if (element instanceof RawObjectReplElement) {
328358
return element.getChildren();
329359
}
360+
if (element instanceof ReplGroup) {
361+
return Promise.resolve(element.getChildren());
362+
}
330363

331364
return (<IExpression>element).getChildren();
332365
}
@@ -343,6 +376,9 @@ export class ReplAccessibilityProvider implements IAccessibilityProvider<IReplEl
343376
if (element instanceof RawObjectReplElement) {
344377
return localize('replRawObjectAriaLabel', "Repl variable {0} has value {1}, read eval print loop, debug", element.name, element.value);
345378
}
379+
if (element instanceof ReplGroup) {
380+
return localize('replGroup', "Repl group {0}, read eval print loop, debug", element.name);
381+
}
346382

347383
return '';
348384
}

src/vs/workbench/contrib/debug/common/replModel.ts

+75-3
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,60 @@ export class ReplEvaluationResult extends ExpressionContainer implements IReplEl
120120
}
121121
}
122122

123+
export class ReplGroup implements IReplElement {
124+
125+
private children: IReplElement[] = [];
126+
private id: string;
127+
private ended = false;
128+
static COUNTER = 0;
129+
130+
constructor(
131+
public name: string,
132+
public autoExpand: boolean,
133+
public sourceData?: IReplElementSource
134+
) {
135+
this.id = `replGroup:${ReplGroup.COUNTER++}`;
136+
}
137+
138+
get hasChildren() {
139+
return true;
140+
}
141+
142+
getId(): string {
143+
return this.id;
144+
}
145+
146+
toString(): string {
147+
return this.name;
148+
}
149+
150+
addChild(child: IReplElement): void {
151+
const lastElement = this.children.length ? this.children[this.children.length - 1] : undefined;
152+
if (lastElement instanceof ReplGroup && !lastElement.hasEnded) {
153+
lastElement.addChild(child);
154+
} else {
155+
this.children.push(child);
156+
}
157+
}
158+
159+
getChildren(): IReplElement[] {
160+
return this.children;
161+
}
162+
163+
end(): void {
164+
const lastElement = this.children.length ? this.children[this.children.length - 1] : undefined;
165+
if (lastElement instanceof ReplGroup && !lastElement.hasEnded) {
166+
lastElement.end();
167+
} else {
168+
this.ended = true;
169+
}
170+
}
171+
172+
get hasEnded(): boolean {
173+
return this.ended;
174+
}
175+
}
176+
123177
export class ReplModel {
124178
private replElements: IReplElement[] = [];
125179
private readonly _onDidChangeElements = new Emitter<void>();
@@ -162,11 +216,29 @@ export class ReplModel {
162216
}
163217
}
164218

219+
startGroup(name: string, autoExpand: boolean, sourceData?: IReplElementSource): void {
220+
const group = new ReplGroup(name, autoExpand, sourceData);
221+
this.addReplElement(group);
222+
}
223+
224+
endGroup(): void {
225+
const lastElement = this.replElements[this.replElements.length - 1];
226+
if (lastElement instanceof ReplGroup) {
227+
lastElement.end();
228+
}
229+
}
230+
165231
private addReplElement(newElement: IReplElement): void {
166-
this.replElements.push(newElement);
167-
if (this.replElements.length > MAX_REPL_LENGTH) {
168-
this.replElements.splice(0, this.replElements.length - MAX_REPL_LENGTH);
232+
const lastElement = this.replElements.length ? this.replElements[this.replElements.length - 1] : undefined;
233+
if (lastElement instanceof ReplGroup && !lastElement.hasEnded) {
234+
lastElement.addChild(newElement);
235+
} else {
236+
this.replElements.push(newElement);
237+
if (this.replElements.length > MAX_REPL_LENGTH) {
238+
this.replElements.splice(0, this.replElements.length - MAX_REPL_LENGTH);
239+
}
169240
}
241+
170242
this._onDidChangeElements.fire();
171243
}
172244

src/vs/workbench/contrib/debug/test/browser/repl.test.ts

+39-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as assert from 'assert';
88
import severity from 'vs/base/common/severity';
99
import { DebugModel, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
1010
import { MockRawSession, MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug';
11-
import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel, ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel';
11+
import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel, ReplEvaluationResult, ReplGroup } from 'vs/workbench/contrib/debug/common/replModel';
1212
import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession';
1313
import { timeout } from 'vs/base/common/async';
1414
import { createMockSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test';
@@ -151,4 +151,42 @@ suite('Debug - REPL', () => {
151151
assert.equal((<ReplEvaluationResult>session.getReplElements()[4]).value, '=after.2');
152152
assert.equal((<SimpleReplElement>session.getReplElements()[5]).value, 'after.2');
153153
});
154+
155+
test('repl groups', async () => {
156+
const session = createMockSession(model);
157+
const repl = new ReplModel();
158+
159+
repl.appendToRepl(session, 'first global line', severity.Info);
160+
repl.startGroup('group_1', true);
161+
repl.appendToRepl(session, 'first line in group', severity.Info);
162+
repl.appendToRepl(session, 'second line in group', severity.Info);
163+
const elements = repl.getReplElements();
164+
assert.equal(elements.length, 2);
165+
const group = elements[1] as ReplGroup;
166+
assert.equal(group.name, 'group_1');
167+
assert.equal(group.autoExpand, true);
168+
assert.equal(group.hasChildren, true);
169+
assert.equal(group.hasEnded, false);
170+
171+
repl.startGroup('group_2', false);
172+
repl.appendToRepl(session, 'first line in subgroup', severity.Info);
173+
repl.appendToRepl(session, 'second line in subgroup', severity.Info);
174+
const children = group.getChildren();
175+
assert.equal(children.length, 3);
176+
assert.equal((<SimpleReplElement>children[0]).value, 'first line in group');
177+
assert.equal((<SimpleReplElement>children[1]).value, 'second line in group');
178+
assert.equal((<ReplGroup>children[2]).name, 'group_2');
179+
assert.equal((<ReplGroup>children[2]).hasEnded, false);
180+
assert.equal((<ReplGroup>children[2]).getChildren().length, 2);
181+
repl.endGroup();
182+
assert.equal((<ReplGroup>children[2]).hasEnded, true);
183+
repl.appendToRepl(session, 'third line in group', severity.Info);
184+
assert.equal(group.getChildren().length, 4);
185+
assert.equal(group.hasEnded, false);
186+
repl.endGroup();
187+
assert.equal(group.hasEnded, true);
188+
repl.appendToRepl(session, 'second global line', severity.Info);
189+
assert.equal(repl.getReplElements().length, 3);
190+
assert.equal((<SimpleReplElement>repl.getReplElements()[2]).value, 'second global line');
191+
});
154192
});

0 commit comments

Comments
 (0)