Skip to content

Commit

Permalink
Support lazy resolving for variable view #134450
Browse files Browse the repository at this point in the history
  • Loading branch information
roblourens committed Feb 20, 2022
1 parent 726f9c9 commit 97606e4
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 24 deletions.
50 changes: 35 additions & 15 deletions src/vs/workbench/contrib/debug/browser/baseDebugView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@
*--------------------------------------------------------------------------------------------*/

import * as dom from 'vs/base/browser/dom';
import { IExpression, IDebugService, IExpressionContainer } from 'vs/workbench/contrib/debug/common/debug';
import { Expression, Variable, ExpressionContainer } from 'vs/workbench/contrib/debug/common/debugModel';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
import { createMatches, FuzzyScore } from 'vs/base/common/filters';
import { once } from 'vs/base/common/functional';
import { KeyCode } from 'vs/base/common/keyCodes';
import { DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
import { IDebugService, IExpression, IExpressionContainer } from 'vs/workbench/contrib/debug/common/debug';
import { Expression, ExpressionContainer, Variable } from 'vs/workbench/contrib/debug/common/debugModel';
import { ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel';
import { once } from 'vs/base/common/functional';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';

export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
export const twistiePixels = 20;
Expand All @@ -33,13 +33,15 @@ export interface IRenderValueOptions {
showHover?: boolean;
colorize?: boolean;
linkDetector?: LinkDetector;
lazyButton?: HTMLElement;
}

export interface IVariableTemplateData {
expression: HTMLElement;
name: HTMLElement;
value: HTMLElement;
label: HighlightedLabel;
lazyButton: HTMLElement;
}

export function renderViewTree(container: HTMLElement): HTMLElement {
Expand Down Expand Up @@ -110,6 +112,7 @@ export function renderVariable(variable: Variable, data: IVariableTemplateData,
data.label.set(':');
}

data.expression.classList.toggle('lazy', !!(variable.presentationHint?.lazy && !variable.isLazyEvaluated));
renderExpressionValue(variable, data.value, {
showChanged,
maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,
Expand All @@ -134,7 +137,10 @@ export interface IExpressionTemplateData {
inputBoxContainer: HTMLElement;
actionBar?: ActionBar;
elementDisposable: IDisposable[];
templateDisposable: IDisposable;
label: HighlightedLabel;
lazyButton: HTMLElement;
currentElement: IExpression | undefined;
}

export abstract class AbstractExpressionsRenderer implements ITreeRenderer<IExpression, FuzzyScore, IExpressionTemplateData> {
Expand All @@ -151,21 +157,35 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer<IExpr
const expression = dom.append(container, $('.expression'));
const name = dom.append(expression, $('span.name'));
const value = dom.append(expression, $('span.value'));
const lazyButton = dom.append(expression, $('span.lazy-button'));
lazyButton.textContent = `...`;

const label = new HighlightedLabel(name);

const inputBoxContainer = dom.append(expression, $('.inputBoxContainer'));

const templateDisposable = new DisposableStore();

let actionBar: ActionBar | undefined;
if (this.renderActionBar) {
dom.append(expression, $('.span.actionbar-spacer'));
actionBar = new ActionBar(expression);
actionBar = templateDisposable.add(new ActionBar(expression));
}

return { expression, name, value, label, inputBoxContainer, actionBar, elementDisposable: [] };
const template: IExpressionTemplateData = { expression, name, value, label, inputBoxContainer, actionBar, elementDisposable: [], templateDisposable, lazyButton, currentElement: undefined };

templateDisposable.add(dom.addDisposableListener(lazyButton, dom.EventType.CLICK, () => {
if (template.currentElement) {
this.debugService.getViewModel().evaluateLazyExpression(template.currentElement);
}
}));

return template;
}

renderElement(node: ITreeNode<IExpression, FuzzyScore>, index: number, data: IExpressionTemplateData): void {
const { element } = node;
data.currentElement = element;
this.renderExpression(element, data, createMatches(node.filterData));
if (data.actionBar) {
this.renderActionBar!(data.actionBar, element, data);
Expand Down Expand Up @@ -243,6 +263,6 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer<IExpr

disposeTemplate(templateData: IExpressionTemplateData): void {
dispose(templateData.elementDisposable);
templateData.actionBar?.dispose();
templateData.templateDisposable.dispose();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
this.updateInlineValuesScheduler.schedule();
}));
this.toDispose.push(this.debugService.getViewModel().onWillUpdateViews(() => this.updateInlineValuesScheduler.schedule()));
this.toDispose.push(this.debugService.getViewModel().onDidEvaluateLazyExpression(() => this.updateInlineValuesScheduler.schedule()));
this.toDispose.push(this.editor.onDidChangeModel(async () => {
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
const model = this.editor.getModel();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@
font-size: 11px;
}

.monaco-workbench .monaco-list-row .expression .value {
.monaco-workbench .monaco-list-row .expression .value,
.monaco-workbench .monaco-list-row .expression .lazy-button {
margin-left: 6px;
}

Expand Down Expand Up @@ -116,6 +117,22 @@
font-style: italic;
}

.monaco-workbench .monaco-list-row .expression .lazy-button {
display: none;
}

.monaco-workbench .monaco-list-row .expression .lazy-button:hover {
text-decoration: underline;
}

.monaco-workbench .monaco-list-row .expression.lazy .lazy-button {
display: inline;
}

.monaco-workbench .monaco-list-row .expression.lazy .value {
display: none;
}

.monaco-workbench .debug-inline-value {
background-color: var(--vscode-editor-inlineValuesBackground);
color: var(--vscode-editor-inlineValuesForeground);
Expand Down
5 changes: 5 additions & 0 deletions src/vs/workbench/contrib/debug/browser/variablesView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ export class VariablesView extends ViewPane {
horizontalScrolling = undefined;
}
}));
this._register(this.debugService.getViewModel().onDidEvaluateLazyExpression(e => {
if (e instanceof Variable) {
this.tree.updateChildren(e, false, true);
}
}));
this._register(this.debugService.onDidEndSession(() => {
this.savedViewState.clear();
this.autoExpandedScopes.clear();
Expand Down
5 changes: 5 additions & 0 deletions src/vs/workbench/contrib/debug/common/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ export interface IReplElementSource {

export interface IExpressionContainer extends ITreeElement {
readonly hasChildren: boolean;
readonly isLazyEvaluated?: boolean;
evaluateLazy(): Promise<void>;
getChildren(): Promise<IExpression[]>;
readonly reference?: number;
readonly value: string;
Expand Down Expand Up @@ -567,7 +569,10 @@ export interface IViewModel extends ITreeElement {
onDidFocusSession: Event<IDebugSession | undefined>;
onDidFocusStackFrame: Event<{ stackFrame: IStackFrame | undefined; explicit: boolean }>;
onDidSelectExpression: Event<{ expression: IExpression; settingWatch: boolean } | undefined>;
onDidEvaluateLazyExpression: Event<IExpressionContainer>;
onWillUpdateViews: Event<void>;

evaluateLazyExpression(expression: IExpressionContainer): void;
}

export interface IEvaluate {
Expand Down
42 changes: 39 additions & 3 deletions src/vs/workbench/contrib/debug/common/debugModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export class ExpressionContainer implements IExpressionContainer {
private _value: string = '';
protected children?: Promise<IExpression[]>;

private _isLazyEvaluated = false;

constructor(
protected session: IDebugSession | undefined,
protected threadId: number | undefined,
Expand All @@ -48,9 +50,14 @@ export class ExpressionContainer implements IExpressionContainer {
public namedVariables: number | undefined = 0,
public indexedVariables: number | undefined = 0,
public memoryReference: string | undefined = undefined,
private startOfVariables: number | undefined = 0
private startOfVariables: number | undefined = 0,
private isLazy = false
) { }

get isLazyEvaluated(): boolean {
return this._isLazyEvaluated;
}

get reference(): number | undefined {
return this._reference;
}
Expand All @@ -60,6 +67,30 @@ export class ExpressionContainer implements IExpressionContainer {
this.children = undefined; // invalidate children cache
}

async evaluateLazy(): Promise<void> {
if (typeof this.reference === 'undefined') {
return;
}

const response = await this.session!.variables(this.reference, this.threadId, undefined, undefined, undefined);
if (!response || !response.body || !response.body.variables || response.body.variables.length !== 1) {
return;
}

const dummyVar = response.body.variables[0];
this.reference = dummyVar.variablesReference;
this._value = dummyVar.value;
this.namedVariables = dummyVar.namedVariables;
this.indexedVariables = dummyVar.indexedVariables;
this.memoryReference = dummyVar.memoryReference;
this._isLazyEvaluated = true;
// Also call overridden method to adopt subclass props
this.adoptLazyResponse(dummyVar);
}

protected adoptLazyResponse(response: DebugProtocol.Variable): void {
}

getChildren(): Promise<IExpression[]> {
if (!this.children) {
this.children = this.doGetChildren();
Expand Down Expand Up @@ -116,7 +147,7 @@ export class ExpressionContainer implements IExpressionContainer {

get hasChildren(): boolean {
// only variables with reference > 0 have children.
return !!this.reference && this.reference > 0;
return !!this.reference && this.reference > 0 && (!this.isLazy || this._isLazyEvaluated);
}

private async fetchVariables(start: number | undefined, count: number | undefined, filter: 'indexed' | 'named' | undefined): Promise<Variable[]> {
Expand Down Expand Up @@ -258,7 +289,7 @@ export class Variable extends ExpressionContainer implements IExpression {
startOfVariables = 0,
idDuplicationIndex = '',
) {
super(session, threadId, reference, `variable:${parent.getId()}:${name}:${idDuplicationIndex}`, namedVariables, indexedVariables, memoryReference, startOfVariables);
super(session, threadId, reference, `variable:${parent.getId()}:${name}:${idDuplicationIndex}`, namedVariables, indexedVariables, memoryReference, startOfVariables, presentationHint?.lazy);
this.value = value || '';
this.type = type;
}
Expand Down Expand Up @@ -294,6 +325,11 @@ export class Variable extends ExpressionContainer implements IExpression {
return this.name ? `${this.name}: ${this.value}` : this.value;
}

protected override adoptLazyResponse(response: DebugProtocol.Variable): void {
this.evaluateName = response.evaluateName;
this.presentationHint = response.presentationHint;
}

toDebugProtocolObject(): DebugProtocol.Variable {
return {
name: this.name,
Expand Down
12 changes: 11 additions & 1 deletion src/vs/workbench/contrib/debug/common/debugViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { Emitter, Event } from 'vs/base/common/event';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_EXPRESSION_SELECTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugSession, IExpression, IStackFrame, IThread, IViewModel } from 'vs/workbench/contrib/debug/common/debug';
import { CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_EXPRESSION_SELECTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugSession, IExpression, IExpressionContainer, IStackFrame, IThread, IViewModel } from 'vs/workbench/contrib/debug/common/debug';
import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils';

export class ViewModel implements IViewModel {
Expand All @@ -19,6 +19,7 @@ export class ViewModel implements IViewModel {
private readonly _onDidFocusSession = new Emitter<IDebugSession | undefined>();
private readonly _onDidFocusStackFrame = new Emitter<{ stackFrame: IStackFrame | undefined; explicit: boolean }>();
private readonly _onDidSelectExpression = new Emitter<{ expression: IExpression; settingWatch: boolean } | undefined>();
private readonly _onDidEvaluateLazyExpression = new Emitter<IExpressionContainer>();
private readonly _onWillUpdateViews = new Emitter<void>();
private expressionSelectedContextKey!: IContextKey<boolean>;
private loadedScriptsSupportedContextKey!: IContextKey<boolean>;
Expand Down Expand Up @@ -121,6 +122,10 @@ export class ViewModel implements IViewModel {
return this._onDidSelectExpression.event;
}

get onDidEvaluateLazyExpression(): Event<IExpressionContainer> {
return this._onDidEvaluateLazyExpression.event;
}

updateViews(): void {
this._onWillUpdateViews.fire();
}
Expand All @@ -136,4 +141,9 @@ export class ViewModel implements IViewModel {
setMultiSessionView(isMultiSessionView: boolean): void {
this.multiSessionDebug.set(isMultiSessionView);
}

async evaluateLazyExpression(expression: IExpressionContainer): Promise<void> {
await expression.evaluateLazy();
this._onDidEvaluateLazyExpression.fire(expression);
}
}
4 changes: 4 additions & 0 deletions src/vs/workbench/contrib/debug/common/replModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ export class RawObjectReplElement implements IExpression {
return (Array.isArray(this.valueObj) && this.valueObj.length > 0) || (isObject(this.valueObj) && Object.getOwnPropertyNames(this.valueObj).length > 0);
}

evaluateLazy(): Promise<void> {
throw new Error('Method not implemented.');
}

getChildren(): Promise<IExpression[]> {
let result: IExpression[] = [];
if (Array.isArray(this.valueObj)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ suite('Debug - Base Debug View', () => {
let name = $('.');
let value = $('.');
const label = new HighlightedLabel(name);
renderVariable(variable, { expression, name, value, label }, false, []);
let lazyButton = $('.');
renderVariable(variable, { expression, name, value, label, lazyButton }, false, []);

assert.strictEqual(label.element.textContent, 'foo');
assert.strictEqual(value.textContent, '');
Expand All @@ -105,7 +106,7 @@ suite('Debug - Base Debug View', () => {
expression = $('.');
name = $('.');
value = $('.');
renderVariable(variable, { expression, name, value, label }, false, [], linkDetector);
renderVariable(variable, { expression, name, value, label, lazyButton }, false, [], linkDetector);
assert.strictEqual(value.textContent, 'hey');
assert.strictEqual(label.element.textContent, 'foo:');
assert.strictEqual(label.element.title, 'string');
Expand All @@ -114,15 +115,15 @@ suite('Debug - Base Debug View', () => {
expression = $('.');
name = $('.');
value = $('.');
renderVariable(variable, { expression, name, value, label }, false, [], linkDetector);
renderVariable(variable, { expression, name, value, label, lazyButton }, false, [], linkDetector);
assert.ok(value.querySelector('a'));
assert.strictEqual(value.querySelector('a')!.textContent, variable.value);

variable = new Variable(session, 1, scope, 2, 'console', 'console', '5', 0, 0, undefined, { kind: 'virtual' });
expression = $('.');
name = $('.');
value = $('.');
renderVariable(variable, { expression, name, value, label }, false, [], linkDetector);
renderVariable(variable, { expression, name, value, label, lazyButton }, false, [], linkDetector);
assert.strictEqual(name.className, 'virtual');
assert.strictEqual(label.element.textContent, 'console:');
assert.strictEqual(label.element.title, 'console');
Expand Down

0 comments on commit 97606e4

Please sign in to comment.