Skip to content

Commit

Permalink
Merge pull request #9346 from jtpio/debugger-scopes
Browse files Browse the repository at this point in the history
Handle multiple scopes in the debugger variables viewer
  • Loading branch information
afshin committed Dec 8, 2020
2 parents d8d7243 + 7e9bedb commit a708c77
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 44 deletions.
1 change: 1 addition & 0 deletions packages/debugger-extension/schema/main.json
Expand Up @@ -36,6 +36,7 @@
"$ref": "#/definitions/variableFilters",
"default": {
"xpython": [
"debugpy",
"display",
"get_ipython",
"ptvsd",
Expand Down
85 changes: 63 additions & 22 deletions packages/debugger/src/panels/variables/grid.ts
Expand Up @@ -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);
Expand All @@ -54,7 +54,24 @@ export class VariablesBodyGrid extends Panel {
*/
set filter(filter: Set<string>) {
(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;
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -258,33 +299,32 @@ 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();
this.emitChanged({
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
});
}

Expand All @@ -301,6 +341,7 @@ class GridModel extends DataModel {
}

private _filter = new Set<string>();
private _scope = '';
private _data: {
name: string[];
type: string[];
Expand Down
18 changes: 15 additions & 3 deletions packages/debugger/src/panels/variables/index.ts
Expand Up @@ -15,6 +15,8 @@ import { VariablesBodyGrid } from './grid';

import { VariablesHeader } from './header';

import { ScopeSwitcher } from './scope';

import { VariablesBodyTree } from './tree';

/**
Expand All @@ -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();
Expand All @@ -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')
})
);
Expand Down Expand Up @@ -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;
}
};

Expand Down
134 changes: 134 additions & 0 deletions 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<HTMLSelectElement>) => {
const value = event.target.value;
setValue(value);
tree.scope = value;
grid.scope = value;
};

return (
<HTMLSelect
onChange={onChange}
value={value}
aria-label={trans.__('Scope')}
>
{scopes.map(scope => (
<option key={scope.name} value={scope.name}>
{trans.__(scope.name)}
</option>
))}
</HTMLSelect>
);
};

/**
* 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 (
<UseSignal signal={this._model.changed} initialSender={this._model}>
{(): JSX.Element => (
<ScopeSwitcherComponent
model={this._model}
trans={this._trans}
tree={this._tree}
grid={this._grid}
/>
)}
</UseSignal>
);
}

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;
}
}

0 comments on commit a708c77

Please sign in to comment.