diff --git a/packages/debugger-extension/schema/main.json b/packages/debugger-extension/schema/main.json index e21a3de76883..e6c3de7f00fe 100644 --- a/packages/debugger-extension/schema/main.json +++ b/packages/debugger-extension/schema/main.json @@ -36,6 +36,7 @@ "$ref": "#/definitions/variableFilters", "default": { "xpython": [ + "debugpy", "display", "get_ipython", "ptvsd", diff --git a/packages/debugger/src/panels/variables/grid.ts b/packages/debugger/src/panels/variables/grid.ts index e7bede98f3cc..aee1e4a9f08f 100644 --- a/packages/debugger/src/panels/variables/grid.ts +++ b/packages/debugger/src/panels/variables/grid.ts @@ -35,12 +35,12 @@ export class VariablesBodyGrid extends Panel { */ constructor(options: VariablesBodyGrid.IOptions) { super(); - const { model, commands, scopes, themeManager } = options; + const { model, commands, themeManager, scopes } = options; this._grid = new Grid({ commands, themeManager }); this._grid.addClass('jp-DebuggerVariables-grid'); this._model = model; this._model.changed.connect((model: VariablesModel): void => { - this._grid.dataModel.setData(model.scopes); + this._update(); }, this); this._grid.dataModel.setData(scopes ?? []); this.addWidget(this._grid); @@ -54,7 +54,24 @@ export class VariablesBodyGrid extends Panel { */ set filter(filter: Set) { (this._grid.dataModel as GridModel).filter = filter; - this._grid.dataModel.setData(this._model.scopes); + this._update(); + } + + /** + * Set the current scope. + * + * @param scope The current scope for the variables. + */ + set scope(scope: string) { + (this._grid.dataModel as GridModel).scope = scope; + this._update(); + } + + /** + * Update the underlying data model + */ + private _update(): void { + this._grid.dataModel.setData(this._model.scopes ?? []); } private _grid: Grid; @@ -139,6 +156,16 @@ class Grid extends Panel { this.update(); } + /** + * Set the scope for the variables data model. + * + * @param scope The scopes for the variables + */ + set scope(scope: string) { + (this._grid.dataModel as GridModel).scope = scope; + this.update(); + } + /** * Get the data model for the data grid. */ @@ -199,6 +226,20 @@ class GridModel extends DataModel { this._filter = filter; } + /** + * Get the current scope for the variables. + */ + get scope(): string { + return this._scope; + } + + /** + * Set the variable scope + */ + set scope(scope: string) { + this._scope = scope; + } + /** * Get the row count for a particular region in the data grid. * @@ -258,9 +299,9 @@ class GridModel extends DataModel { } /** - * Set the datagrid model data from the variable scopes. + * Set the datagrid model data from the list of variables. * - * @param scopes The scopes. + * @param variables The list of variables. */ setData(scopes: IDebugger.IScope[]): void { this._clearData(); @@ -268,23 +309,22 @@ class GridModel extends DataModel { type: 'model-reset', region: 'body' }); - scopes.forEach(scope => { - const filtered = scope.variables.filter( - variable => - variable.evaluateName && !this._filter.has(variable.evaluateName) - ); - filtered.forEach((variable, index) => { - this._data.name[index] = variable.evaluateName!; - this._data.type[index] = variable.type || ''; - this._data.value[index] = variable.value; - this._data.variablesReference[index] = variable.variablesReference; - }); - this.emitChanged({ - type: 'rows-inserted', - region: 'body', - index: 1, - span: filtered.length - }); + const scope = scopes.find(scope => scope.name === this._scope) ?? scopes[0]; + const variables = scope?.variables ?? []; + const filtered = variables.filter( + variable => variable.name && !this._filter.has(variable.name) + ); + filtered.forEach((variable, index) => { + this._data.name[index] = variable.name; + this._data.type[index] = variable.type ?? ''; + this._data.value[index] = variable.value; + this._data.variablesReference[index] = variable.variablesReference; + }); + this.emitChanged({ + type: 'rows-inserted', + region: 'body', + index: 1, + span: filtered.length }); } @@ -301,6 +341,7 @@ class GridModel extends DataModel { } private _filter = new Set(); + private _scope = ''; private _data: { name: string[]; type: string[]; diff --git a/packages/debugger/src/panels/variables/index.ts b/packages/debugger/src/panels/variables/index.ts index d965165c5b5a..487a517032b8 100644 --- a/packages/debugger/src/panels/variables/index.ts +++ b/packages/debugger/src/panels/variables/index.ts @@ -15,6 +15,8 @@ import { VariablesBodyGrid } from './grid'; import { VariablesHeader } from './header'; +import { ScopeSwitcher } from './scope'; + import { VariablesBodyTree } from './tree'; /** @@ -37,7 +39,7 @@ export class Variables extends Panel { this._table = new VariablesBodyGrid({ model, commands, themeManager }); this._table.hide(); - const onClick = (): void => { + const onViewChange = (): void => { if (this._table.isHidden) { this._tree.hide(); this._table.show(); @@ -50,11 +52,21 @@ export class Variables extends Panel { this.update(); }; + this._header.toolbar.addItem( + 'scope-switcher', + new ScopeSwitcher({ + translator, + model, + tree: this._tree, + grid: this._table + }) + ); + this._header.toolbar.addItem( 'view-VariableSwitch', new ToolbarButton({ iconClass: 'jp-ToggleSwitch', - onClick, + onClick: onViewChange, tooltip: trans.__('Table / Tree View') }) ); @@ -115,7 +127,7 @@ export const convertType = (variable: IDebugger.IVariable): string | number => { case 'str': return value.slice(1, value.length - 1); default: - return type ?? ''; + return type ?? value; } }; diff --git a/packages/debugger/src/panels/variables/scope.tsx b/packages/debugger/src/panels/variables/scope.tsx new file mode 100644 index 000000000000..88dd4e9ac4f4 --- /dev/null +++ b/packages/debugger/src/panels/variables/scope.tsx @@ -0,0 +1,134 @@ +import { ReactWidget, UseSignal } from '@jupyterlab/apputils'; + +import { + ITranslator, + nullTranslator, + TranslationBundle +} from '@jupyterlab/translation'; + +import { HTMLSelect } from '@jupyterlab/ui-components'; + +import React, { useState } from 'react'; + +import { IDebugger } from '../../tokens'; + +import { VariablesBodyGrid } from './grid'; + +import { VariablesBodyTree } from './tree'; + +/** + * A React component to handle scope changes. + * + * @param {object} props The component props. + * @param props.model The variables model. + * @param props.tree The variables tree widget. + * @param props.grid The variables grid widget. + * @param props.trans The translation bundle. + */ +const ScopeSwitcherComponent = ({ + model, + tree, + grid, + trans +}: { + model: IDebugger.Model.IVariables; + tree: VariablesBodyTree; + grid: VariablesBodyGrid; + trans: TranslationBundle; +}): JSX.Element => { + const [value, setValue] = useState('-'); + const scopes = model.scopes; + + const onChange = (event: React.ChangeEvent) => { + const value = event.target.value; + setValue(value); + tree.scope = value; + grid.scope = value; + }; + + return ( + + {scopes.map(scope => ( + + ))} + + ); +}; + +/** + * A widget to switch between scopes. + */ +export class ScopeSwitcher extends ReactWidget { + /** + * Instantiate a new scope switcher. + * + * @param options The instantiation options for a ScopeSwitcher + */ + constructor(options: ScopeSwitcher.IOptions) { + super(); + const { translator, model, tree, grid } = options; + this._model = model; + this._tree = tree; + this._grid = grid; + this._trans = (translator || nullTranslator).load('jupyterlab'); + } + + /** + * Render the scope switcher. + */ + render(): JSX.Element { + return ( + + {(): JSX.Element => ( + + )} + + ); + } + + private _model: IDebugger.Model.IVariables; + private _tree: VariablesBodyTree; + private _grid: VariablesBodyGrid; + private _trans: TranslationBundle; +} + +/** + * A namespace for ScopeSwitcher statics + */ +export namespace ScopeSwitcher { + /** + * The ScopeSwitcher instantiation options. + */ + export interface IOptions { + /** + * The variables model. + */ + model: IDebugger.Model.IVariables; + + /** + * The variables tree viewer. + */ + tree: VariablesBodyTree; + + /** + * The variables table viewer. + */ + grid: VariablesBodyGrid; + + /** + * An optional translator. + */ + translator?: ITranslator; + } +} diff --git a/packages/debugger/src/panels/variables/tree.tsx b/packages/debugger/src/panels/variables/tree.tsx index e14777180342..a0f4c1920f3a 100644 --- a/packages/debugger/src/panels/variables/tree.tsx +++ b/packages/debugger/src/panels/variables/tree.tsx @@ -9,12 +9,13 @@ import { ArrayExt } from '@lumino/algorithm'; import React, { useEffect, useState } from 'react'; -import { IDebugger } from '../../tokens'; +import { DebugProtocol } from 'vscode-debugprotocol'; -import { convertType } from '.'; +import { IDebugger } from '../../tokens'; import { VariablesModel } from './model'; -import { DebugProtocol } from 'vscode-debugprotocol'; + +import { convertType } from '.'; /** * The body for tree of variables. @@ -39,17 +40,18 @@ export class VariablesBodyTree extends ReactWidget { * Render the VariablesBodyTree. */ render(): JSX.Element { - return ( - <> - {this._scopes.map(scope => ( - - ))} - + const scope = + this._scopes.find(scope => scope.name === this._scope) ?? this._scopes[0]; + + return scope ? ( + + ) : ( +
); } @@ -61,6 +63,14 @@ export class VariablesBodyTree extends ReactWidget { this.update(); } + /** + * Set the current scope + */ + set scope(scope: string) { + this._scope = scope; + this.update(); + } + /** * Update the scopes and the tree of variables. * @@ -74,6 +84,7 @@ export class VariablesBodyTree extends ReactWidget { this.update(); } + private _scope = ''; private _scopes: IDebugger.IScope[] = []; private _filter = new Set(); private _service: IDebugger; @@ -109,7 +120,7 @@ const VariablesComponent = ({ variable => !(filter || new Set()).has(variable.evaluateName || '') ) .map(variable => { - const key = `${variable.evaluateName}-${variable.type}-${variable.value}`; + const key = `${variable.name}-${variable.evaluateName}-${variable.type}-${variable.value}`; return ( { + return scopes.map((scope, i) => { return { name: scope.name, - variables: variables.map(variable => { + variables: variables[i].map(variable => { return { ...variable }; }) }; @@ -639,7 +639,9 @@ export class DebuggerService implements IDebugger, IDisposable { return; } const scopes = await this._getScopes(frame); - const variables = await this._getVariables(scopes[0]); + const variables = await Promise.all( + scopes.map(scope => this._getVariables(scope)) + ); const variableScopes = this._convertScopes(scopes, variables); this._model.variables.scopes = variableScopes; }