diff --git a/src/breakpoints/index.ts b/src/breakpoints/index.ts index 55da8c89..ab61d5dd 100644 --- a/src/breakpoints/index.ts +++ b/src/breakpoints/index.ts @@ -4,9 +4,13 @@ import { Toolbar, ToolbarButton } from '@jupyterlab/apputils'; import { IDisposable } from '@phosphor/disposable'; import { Signal } from '@phosphor/signaling'; + import { Panel, PanelLayout, Widget } from '@phosphor/widgets'; + import { DebugProtocol } from 'vscode-debugprotocol'; + import { IDebugger } from '../tokens'; + import { Body } from './body'; export class Breakpoints extends Panel { diff --git a/src/service.ts b/src/service.ts index 0dd87cec..c9f792fa 100644 --- a/src/service.ts +++ b/src/service.ts @@ -296,6 +296,7 @@ export class DebugService implements IDebugger { } this._model.callstackModel.currentFrameChanged.connect(this.onChangeFrame); + this._model.variablesModel.variableExpanded.connect(this.getVariable); }; onChangeFrame = (_: Callstack.Model, update: Callstack.IFrame) => { @@ -328,6 +329,29 @@ export class DebugService implements IDebugger { return reply.body.scopes; }; + getVariable = async (_: any, variable: DebugProtocol.Variable) => { + const reply = await this.session.sendRequest('variables', { + variablesReference: variable.variablesReference + }); + let newVariable = { ...variable, expanded: true }; + + reply.body.variables.forEach((variable: DebugProtocol.Variable) => { + newVariable = { [variable.name]: variable, ...newVariable }; + }); + + const newScopes = this._model.variablesModel.scopes.map(scope => { + const findIndex = scope.variables.findIndex( + ele => ele.variablesReference === variable.variablesReference + ); + scope.variables[findIndex] = newVariable; + return { ...scope }; + }); + + this._model.variablesModel.scopes = [...newScopes]; + + return reply.body.variables; + }; + getVariables = async (scopes: DebugProtocol.Scope[]) => { if (!scopes || scopes.length === 0) { return; diff --git a/src/variables/body/index.tsx b/src/variables/body/index.tsx index 7641bb57..6ed77350 100644 --- a/src/variables/body/index.tsx +++ b/src/variables/body/index.tsx @@ -3,11 +3,12 @@ import { Variables } from '../index'; -import { ITheme, ObjectInspector, ObjectLabel } from 'react-inspector'; +import { ITheme, ObjectInspector, ObjectRootLabel } from 'react-inspector'; import { ReactWidget } from '@jupyterlab/apputils'; import { ArrayExt } from '@phosphor/algorithm'; + import React, { useEffect, useState } from 'react'; export class Body extends ReactWidget { @@ -27,12 +28,70 @@ export class Body extends ReactWidget { const VariableComponent = ({ model }: { model: Variables.Model }) => { const [data, setData] = useState(model.scopes); + // TODO: this should be simplified and extracted from the component + const filterVariable = ( + variable: Variables.IVariable, + isObject?: boolean, + keyObj?: string + ): Object => { + const tableKey = ['name', 'value', 'type']; + const filteredObj = Object.keys(variable) + .filter(key => { + if (isObject && key === 'value') { + return true; + } + if (tableKey.includes(key)) { + return true; + } + if ( + typeof (variable as any)[key] === 'object' && + key !== 'presentationHint' + ) { + return true; + } + return false; + }) + .reduce((res, key) => { + let valueOfKey = + key === 'value' ? convertType(variable) : (variable as any)[key]; + if (typeof valueOfKey === 'object') { + return { ...res, ...filterVariable(valueOfKey, true, key) }; + } + if (isObject) { + return { ...res, [keyObj]: valueOfKey }; + } + + return { + ...res, + [key]: valueOfKey + }; + }, {}); + + return filteredObj; + }; + + const convertForObjectInspector = (scopes: Variables.IScope[]) => { + return scopes.map(scope => { + const newVariable = scope.variables.map(variable => { + if (variable.expanded || variable.variablesReference === 0) { + return { ...filterVariable(variable) }; + } else { + return { + expandVariable: () => model.expandVariable(variable), + ...filterVariable(variable) + }; + } + }); + return { name: scope.name, variables: newVariable }; + }); + }; + useEffect(() => { - const updateScopes = () => { - if (ArrayExt.shallowEqual(data, model.scopes)) { + const updateScopes = (self: Variables.Model) => { + if (ArrayExt.shallowEqual(data, self.scopes)) { return; } - setData(model.scopes); + setData(self.scopes); }; model.changed.connect(updateScopes); @@ -44,11 +103,11 @@ const VariableComponent = ({ model }: { model: Variables.Model }) => { const List = () => ( <> - {data.map(scopes => ( + {convertForObjectInspector(data).map(scope => ( { return <>{List()}; }; +const convertType = (variable: Variables.IVariable) => { + const { type, value } = variable; + switch (type) { + case 'int': + return parseInt(value, 10); + case 'float': + return parseFloat(value); + case 'bool': + return value === 'False' ? false : true; + case 'str': + return value.slice(1, value.length - 1); + default: + return value; + } +}; + const defaultNodeRenderer = ({ depth, name, @@ -75,8 +150,16 @@ const defaultNodeRenderer = ({ expanded: boolean; theme?: string | Partial; }) => { + const types = ['bool', 'str', 'int', 'float']; const label = data.name === '' || data.name == null ? name : data.name; - const value = data.value; + const value = types.includes(data.type) ? data.value : data.type; + + useEffect(() => { + if (!expanded || !data.expandVariable) { + return; + } + data.expandVariable(); + }); return depth === 0 ? ( @@ -88,10 +171,10 @@ const defaultNodeRenderer = ({ {label} : - {value} + {value} ) : ( - + ); }; diff --git a/src/variables/index.ts b/src/variables/index.ts index eb734ed0..1af306c5 100644 --- a/src/variables/index.ts +++ b/src/variables/index.ts @@ -52,7 +52,9 @@ class VariablesHeader extends Widget { } export namespace Variables { - export interface IVariable extends DebugProtocol.Variable {} + export interface IVariable extends DebugProtocol.Variable { + expanded?: boolean; + } export interface IScope { name: string; @@ -68,18 +70,6 @@ export namespace Variables { return this._changed; } - get currentVariable(): IVariable { - return this._currentVariable; - } - - set currentVariable(variable: IVariable) { - if (this._currentVariable === variable) { - return; - } - this._currentVariable = variable; - this._currentVariableChanged.emit(variable); - } - get scopes(): IScope[] { return this._state; } @@ -89,26 +79,17 @@ export namespace Variables { this._changed.emit(); } - get variables(): IVariable[] { - return this._currentScope ? this._currentScope.variables : []; + get variableExpanded(): ISignal { + return this._variableExpanded; } - set variables(variables: IVariable[]) { - this._currentScope.variables = variables; - this._changed.emit(); - } - - getCurrentVariables(): IVariable[] { - return this.variables; + expandVariable(variable: IVariable) { + this._variableExpanded.emit(variable); } protected _state: IScope[]; - - private _currentVariable: IVariable; - private _currentScope: IScope; - + private _variableExpanded = new Signal(this); private _changed = new Signal(this); - private _currentVariableChanged = new Signal(this); } export interface IOptions extends Panel.IOptions {