import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { DocumentManager } from '../util/DocumentManager';
import { CompileScript, Transformer, ts } from '../util/Scripting';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { undoable } from '../util/UndoManager';
import { DocumentIconContainer } from './nodes/DocumentIcon';
import { OverlayView } from './OverlayView';
import './ScriptingRepl.scss';
@observer
export class ScriptingObjectDisplay extends React.Component<{ scrollToBottom: () => void; value: { [key: string]: any }; name?: string }> {
@observable collapsed = true;
@action
toggle = () => {
this.collapsed = !this.collapsed;
this.props.scrollToBottom();
};
render() {
const val = this.props.value;
const proto = Object.getPrototypeOf(val);
const name = (proto && proto.constructor && proto.constructor.name) || String(val);
const title = this.props.name ? (
<>
{this.props.name} :
{name}
>
) : (
name
);
if (this.collapsed) {
return (
{title} (+{Object.keys(val).length})
);
} else {
return (
{title}
{Object.keys(val).map(key => (
))}
);
}
}
}
@observer
export class ScriptingValueDisplay extends React.Component<{ scrollToBottom: () => void; value: any; name?: string }> {
render() {
const val = this.props.name ? this.props.value[this.props.name] : this.props.value;
if (typeof val === 'object') {
return ;
} else if (typeof val === 'function') {
const name = '[Function]';
const title = this.props.name ? (
<>
{this.props.name} :
{name}
>
) : (
name
);
return {title}
;
} else {
const name = String(val);
const title = this.props.name ? (
<>
{this.props.name} :
{name}
>
) : (
name
);
return {title}
;
}
}
}
@observer
export class ScriptingRepl extends React.Component {
@observable private commands: { command: string; result: any }[] = [];
private commandsHistory: string[] = [];
@observable private commandString: string = '';
private commandBuffer: string = '';
@observable private historyIndex: number = -1;
private commandsRef = React.createRef();
private args: any = {};
getTransformer = (): Transformer => {
return {
transformer: context => {
const knownVars: { [name: string]: number } = {};
const usedDocuments: number[] = [];
ScriptingGlobals.getGlobals().forEach((global: any) => (knownVars[global] = 1));
return root => {
function visit(node: ts.Node) {
let skip = false;
if (ts.isIdentifier(node)) {
if (ts.isParameter(node.parent)) {
skip = true;
knownVars[node.text] = 1;
}
}
node = ts.visitEachChild(node, visit, context);
if (ts.isIdentifier(node)) {
const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
if (ts.isParameter(node.parent)) {
// delete knownVars[node.text];
} else if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
const match = node.text.match(/d([0-9]+)/);
if (match) {
const m = parseInt(match[1]);
usedDocuments.push(m);
} else {
return ts.createPropertyAccess(ts.createIdentifier('args'), node);
}
}
}
return node;
}
return ts.visitNode(root, visit);
};
},
};
};
@action
onKeyDown = (e: React.KeyboardEvent) => {
let stopProp = true;
switch (e.key) {
case 'Enter': {
e.stopPropagation();
const docGlobals: { [name: string]: any } = {};
DocumentManager.Instance.DocumentViews.forEach((dv, i) => (docGlobals[`d${i}`] = dv.props.Document));
const globals = ScriptingGlobals.makeMutableGlobalsCopy(docGlobals);
const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: 'any' }, transformer: this.getTransformer(), globals });
if (!script.compiled) {
this.commands.push({ command: this.commandString, result: script.errors });
return;
}
const result = undoable(() => script.run({ args: this.args }, e => this.commands.push({ command: this.commandString, result: e.toString() })), 'run:' + this.commandString)();
if (result.success) {
this.commands.push({ command: this.commandString, result: result.result });
this.commandsHistory.push(this.commandString);
this.maybeScrollToBottom();
this.commandString = '';
this.commandBuffer = '';
this.historyIndex = -1;
}
break;
}
case 'ArrowUp': {
if (this.historyIndex < this.commands.length - 1) {
this.historyIndex++;
if (this.historyIndex === 0) {
this.commandBuffer = this.commandString;
}
this.commandString = this.commandsHistory[this.commands.length - 1 - this.historyIndex];
}
break;
}
case 'ArrowDown': {
if (this.historyIndex >= 0) {
this.historyIndex--;
if (this.historyIndex === -1) {
this.commandString = this.commandBuffer;
this.commandBuffer = '';
} else {
this.commandString = this.commandsHistory[this.commands.length - 1 - this.historyIndex];
}
}
break;
}
default:
stopProp = false;
break;
}
if (stopProp) {
e.stopPropagation();
e.preventDefault();
}
};
@action
onChange = (e: React.ChangeEvent) => {
this.commandString = e.target.value;
};
private shouldScroll: boolean = false;
private maybeScrollToBottom = () => {
const ele = this.commandsRef.current;
if (ele && ele.scrollTop === ele.scrollHeight - ele.offsetHeight) {
this.shouldScroll = true;
this.forceUpdate();
}
};
private scrollToBottom() {
const ele = this.commandsRef.current;
ele && ele.scroll({ behavior: 'auto', top: ele.scrollHeight });
}
componentDidUpdate() {
if (this.shouldScroll) {
this.shouldScroll = false;
this.scrollToBottom();
}
}
overlayDisposer?: () => void;
onFocus = () => {
this.overlayDisposer?.();
this.overlayDisposer = OverlayView.Instance.addElement(, { x: 0, y: 0 });
};
onBlur = () => this.overlayDisposer?.();
render() {
return (
);
}
}