@jupyterlab/debugger
Version:
JupyterLab - Debugger Extension
216 lines • 9.59 kB
JavaScript
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { nullTranslator } from '@jupyterlab/translation';
import { getTreeItemElement, ReactWidget, searchIcon } from '@jupyterlab/ui-components';
import { Button, TreeItem, TreeView } from '@jupyter/react-components';
import { ArrayExt } from '@lumino/algorithm';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { convertType } from '.';
import { Debugger } from '../../debugger';
/**
* The body for tree of variables.
*/
export class VariablesBodyTree extends ReactWidget {
/**
* Instantiate a new Body for the tree of variables.
*
* @param options The instantiation options for a VariablesBodyTree.
*/
constructor(options) {
super();
this._scope = '';
this._scopes = [];
this._filter = new Set();
this._commands = options.commands;
this._service = options.service;
this._translator = options.translator;
const model = (this.model = options.model);
model.changed.connect(this._updateScopes, this);
this.addClass('jp-DebuggerVariables-body');
}
/**
* Render the VariablesBodyTree.
*/
render() {
var _a;
const scope = (_a = this._scopes.find(scope => scope.name === this._scope)) !== null && _a !== void 0 ? _a : this._scopes[0];
const handleSelectVariable = (variable) => {
this.model.selectedVariable = variable;
};
if ((scope === null || scope === void 0 ? void 0 : scope.name) !== 'Globals') {
this.addClass('jp-debuggerVariables-local');
}
else {
this.removeClass('jp-debuggerVariables-local');
}
return scope ? (React.createElement(React.Fragment, null,
React.createElement(TreeView, { className: "jp-TreeView" },
React.createElement(VariablesBranch, { key: scope.name, commands: this._commands, service: this._service, data: scope.variables, filter: this._filter, translator: this._translator, handleSelectVariable: handleSelectVariable })))) : (React.createElement("div", null));
}
/**
* Set the variable filter list.
*/
set filter(filter) {
this._filter = filter;
this.update();
}
/**
* Set the current scope
*/
set scope(scope) {
this._scope = scope;
this.update();
}
/**
* Update the scopes and the tree of variables.
*
* @param model The variables model.
*/
_updateScopes(model) {
if (ArrayExt.shallowEqual(this._scopes, model.scopes)) {
return;
}
this._scopes = model.scopes;
this.update();
}
}
/**
* A React component to display a list of variables.
*
* @param {object} props The component props.
* @param props.data An array of variables.
* @param props.service The debugger service.
* @param props.filter Optional variable filter list.
*/
const VariablesBranch = (props) => {
const { commands, data, service, filter, translator, handleSelectVariable } = props;
const [variables, setVariables] = useState(data);
useEffect(() => {
setVariables(data);
}, [data]);
return (React.createElement(React.Fragment, null, variables
.filter(variable => !(filter || new Set()).has(variable.evaluateName || ''))
.map(variable => {
const key = `${variable.name}-${variable.evaluateName}-${variable.type}-${variable.value}-${variable.variablesReference}`;
return (React.createElement(VariableComponent, { key: key, commands: commands, data: variable, service: service, filter: filter, translator: translator, onSelect: handleSelectVariable }));
})));
};
function _prepareDetail(variable) {
if (variable.type === 'float' &&
(variable.value == 'inf' || variable.value == '-inf')) {
return variable.value;
}
const detail = convertType(variable);
if (variable.type === 'float' && isNaN(detail)) {
// silence React warning:
// `Received NaN for the `children` attribute. If this is expected, cast the value to a string`
return 'NaN';
}
return detail;
}
/**
* A React component to display one node variable in tree.
*
* @param {object} props The component props.
* @param props.data An array of variables.
* @param props.service The debugger service.
* @param props.filter Optional variable filter list.
*/
const VariableComponent = (props) => {
var _a, _b;
const { commands, data, service, filter, translator, onSelect } = props;
const [variable] = useState(data);
const [showDetailsButton, setShowDetailsButton] = useState(false);
const [expanded, setExpanded] = useState(false);
const [variables, setVariables] = useState(null);
const trans = useMemo(() => (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab'), [translator]);
const onSelection = onSelect !== null && onSelect !== void 0 ? onSelect : (() => void 0);
const expandable = useMemo(() => variable.variablesReference !== 0 || variable.type === 'function', [variable.variablesReference, variable.type]);
const details = useMemo(() => _prepareDetail(variable), [variable]);
const hasMimeRenderer = useMemo(() => ![
'special variables',
'protected variables',
'function variables',
'class variables'
].includes(variable.name), [variable.name]);
const disableMimeRenderer = useMemo(() => {
var _a;
return !service.model.hasRichVariableRendering ||
!commands.isEnabled(Debugger.CommandIDs.renderMimeVariable, {
name: variable.name,
frameID: (_a = service.model.callstack.frame) === null || _a === void 0 ? void 0 : _a.id
});
}, [
service.model.hasRichVariableRendering,
variable.name,
(_a = service.model.callstack.frame) === null || _a === void 0 ? void 0 : _a.id
]);
const fetchChildren = useCallback(async () => {
if (expandable && !variables) {
setVariables(await service.inspectVariable(variable.variablesReference));
}
}, [expandable, service, variable.variablesReference, variables]);
const onVariableClicked = useCallback(async (event) => {
const item = getTreeItemElement(event.target);
if (event.currentTarget !== item) {
return;
}
if (!expandable) {
return;
}
setExpanded(!expanded);
}, [expandable, expanded]);
const onSelectChange = useCallback((event) => {
if (event.currentTarget === event.detail && event.detail.selected) {
onSelection(variable);
}
}, [variable]);
const renderVariable = useCallback(() => {
var _a;
commands
.execute(Debugger.CommandIDs.renderMimeVariable, {
name: variable.name,
frameID: (_a = service.model.callstack.frame) === null || _a === void 0 ? void 0 : _a.id
})
.catch(reason => {
console.error(`Failed to render variable ${variable === null || variable === void 0 ? void 0 : variable.name}`, reason);
});
}, [commands, variable.name, (_b = service.model.callstack.frame) === null || _b === void 0 ? void 0 : _b.id]);
const onContextMenu = useCallback((event) => {
const item = getTreeItemElement(event.target);
if (event.currentTarget !== item) {
return;
}
onSelection(variable);
}, [variable]);
return (React.createElement(TreeItem, { className: "jp-TreeItem nested", expanded: expanded, onSelect: onSelectChange, onExpand: fetchChildren, onClick: (e) => onVariableClicked(e), onContextMenu: onContextMenu, onKeyDown: event => {
if (event.key == 'Enter') {
if (hasMimeRenderer && showDetailsButton) {
onSelection(variable);
renderVariable();
}
}
}, onFocus: event => {
setShowDetailsButton(!event.defaultPrevented);
event.preventDefault();
}, onBlur: event => {
setShowDetailsButton(false);
}, onMouseOver: (event) => {
setShowDetailsButton(!event.defaultPrevented);
event.preventDefault();
}, onMouseLeave: (event) => {
setShowDetailsButton(false);
} },
React.createElement("span", { className: "jp-DebuggerVariables-name" }, variable.name),
details && (React.createElement("span", { className: "jp-DebuggerVariables-detail" }, details)),
hasMimeRenderer && showDetailsButton && (React.createElement(Button, { className: "jp-DebuggerVariables-renderVariable", appearance: "stealth", slot: "end", disabled: disableMimeRenderer, onClick: e => {
e.stopPropagation();
renderVariable();
}, title: trans.__('Render variable: %1', variable === null || variable === void 0 ? void 0 : variable.name) },
React.createElement(searchIcon.react, { tag: null }))),
variables ? (React.createElement(VariablesBranch, { key: variable.name, commands: commands, data: variables, service: service, filter: filter, translator: translator, handleSelectVariable: onSelect })) : (
/* Trick to ensure collapse button is displayed
when variables are not loaded yet */
expandable && React.createElement(TreeItem, null))));
};
//# sourceMappingURL=tree.js.map