mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Remove REPL code
Summary: Remove repl.py, REPLTab.js, some wiring code, CSS, and a test in testscript.json. Test Plan: NA Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2923
This commit is contained in:
parent
67aca9ccf6
commit
1f6e693b6e
@ -12,7 +12,6 @@ import {DocComm, DocUserAction} from 'app/client/components/DocComm';
|
|||||||
import * as DocConfigTab from 'app/client/components/DocConfigTab';
|
import * as DocConfigTab from 'app/client/components/DocConfigTab';
|
||||||
import * as GridView from 'app/client/components/GridView';
|
import * as GridView from 'app/client/components/GridView';
|
||||||
import {Importer} from 'app/client/components/Importer';
|
import {Importer} from 'app/client/components/Importer';
|
||||||
import * as REPLTab from 'app/client/components/REPLTab';
|
|
||||||
import {ActionGroupWithCursorPos, UndoStack} from 'app/client/components/UndoStack';
|
import {ActionGroupWithCursorPos, UndoStack} from 'app/client/components/UndoStack';
|
||||||
import {ViewLayout} from 'app/client/components/ViewLayout';
|
import {ViewLayout} from 'app/client/components/ViewLayout';
|
||||||
import {get as getBrowserGlobals} from 'app/client/lib/browserGlobals';
|
import {get as getBrowserGlobals} from 'app/client/lib/browserGlobals';
|
||||||
@ -80,7 +79,7 @@ export interface TabOptions {
|
|||||||
category?: any;
|
category?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RightPanelTool = StringUnion("none", "docHistory", "validations", "repl");
|
const RightPanelTool = StringUnion("none", "docHistory", "validations");
|
||||||
|
|
||||||
export interface IExtraTool {
|
export interface IExtraTool {
|
||||||
icon: IconName;
|
icon: IconName;
|
||||||
@ -248,13 +247,6 @@ export class GristDoc extends DisposableWithEvents {
|
|||||||
|
|
||||||
this.autoDispose(DocConfigTab.create({gristDoc: this}));
|
this.autoDispose(DocConfigTab.create({gristDoc: this}));
|
||||||
|
|
||||||
const replTab = this.autoDispose(REPLTab.create(this));
|
|
||||||
this.autoDispose(this.addOptionsTab(
|
|
||||||
'REPL', dom('span.glyphicon.glyphicon-console'),
|
|
||||||
replTab.buildConfigDomObj(),
|
|
||||||
{ hideSearchContent: true }
|
|
||||||
));
|
|
||||||
|
|
||||||
this.rightPanelTool = Computed.create(this, (use) => this._getToolContent(use(this._rightPanelTool)));
|
this.rightPanelTool = Computed.create(this, (use) => this._getToolContent(use(this._rightPanelTool)));
|
||||||
|
|
||||||
this.comparison = options.comparison || null;
|
this.comparison = options.comparison || null;
|
||||||
@ -738,10 +730,6 @@ export class GristDoc extends DisposableWithEvents {
|
|||||||
const content = this._rightPanelTabs.get("Validate Data");
|
const content = this._rightPanelTabs.get("Validate Data");
|
||||||
return content ? {icon: 'Validation', label: 'Validation Rules', content} : null;
|
return content ? {icon: 'Validation', label: 'Validation Rules', content} : null;
|
||||||
}
|
}
|
||||||
case 'repl': {
|
|
||||||
const content = this._rightPanelTabs.get("REPL");
|
|
||||||
return content ? {icon: 'Repl', label: 'REPL', content} : null;
|
|
||||||
}
|
|
||||||
case 'none':
|
case 'none':
|
||||||
default: {
|
default: {
|
||||||
return null;
|
return null;
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
.repl-container {
|
|
||||||
padding: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repl-text {
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
font-family: monospace;
|
|
||||||
white-space: pre;
|
|
||||||
tab-size: 4;
|
|
||||||
-moz-tab-size: 4;
|
|
||||||
-o-tab-size: 4;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
.repl-field {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.repl-error {
|
|
||||||
color: #D00;
|
|
||||||
}
|
|
||||||
.repl-text_line:hover {
|
|
||||||
background-color: #E5E5E5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.re-eval_line_button {
|
|
||||||
cursor: pointer;
|
|
||||||
float: right;
|
|
||||||
text-align: center;
|
|
||||||
width: 15px;
|
|
||||||
color: #808080;
|
|
||||||
}
|
|
||||||
.erase_line_button {
|
|
||||||
cursor: pointer;
|
|
||||||
float: right;
|
|
||||||
text-align: center;
|
|
||||||
width: 15px;
|
|
||||||
color: #808080;
|
|
||||||
}
|
|
||||||
.re-eval_line_button:hover {
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
.erase_line_button:hover {
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unselectable {
|
|
||||||
-webkit-touch-callout: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-khtml-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pointer_group {
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
width: 25px;
|
|
||||||
}
|
|
||||||
.pointer {
|
|
||||||
font-family: monospace;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repl-newline {
|
|
||||||
font-family: monospace;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 2px 10px 15px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repl-cursor_editor {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
.repl-text_editor {
|
|
||||||
position: absolute;
|
|
||||||
padding: 2px 0 0 0;
|
|
||||||
min-width: 10px;
|
|
||||||
min-height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border: none;
|
|
||||||
resize: none;
|
|
||||||
box-shadow: none;
|
|
||||||
outline: none;
|
|
||||||
background: transparent;
|
|
||||||
z-index: 10;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repl-content_measure {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
padding-top: 2px;
|
|
||||||
padding-right: 1em;
|
|
||||||
border: none;
|
|
||||||
visibility: hidden;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formula-text {
|
|
||||||
font-family: monospace;
|
|
||||||
tab-size: 4;
|
|
||||||
-moz-tab-size: 4;
|
|
||||||
-o-tab-size: 4;
|
|
||||||
}
|
|
@ -1,318 +0,0 @@
|
|||||||
/**
|
|
||||||
* A Tab that contains a REPL.
|
|
||||||
* The REPL allows the user to write snippets of python code and see the results of evaluating
|
|
||||||
* them. In particular, the REPL has access to the usercode module, so they can see the results
|
|
||||||
* of quick operations on their data.
|
|
||||||
* The REPL supports evaluation of code, removal of lines from history, and re-computation
|
|
||||||
* and editing of older lines.
|
|
||||||
*/
|
|
||||||
var kd = require('../lib/koDom');
|
|
||||||
var ko = require('knockout');
|
|
||||||
var dom = require('../lib/dom');
|
|
||||||
var Base = require('./Base');
|
|
||||||
var commands = require('./commands');
|
|
||||||
|
|
||||||
var NEW_LINE = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hard tab used instead of soft tabs, as soft-tabs would require a lot of additional
|
|
||||||
* editor logic (partial-width tabs, backspacing a tab, ...) for
|
|
||||||
* which we may want to eventually use a 3rd-party library for in addition to syntax highlighting, etc
|
|
||||||
*/
|
|
||||||
var INDENT_STR = "\t";
|
|
||||||
|
|
||||||
function REPLTab(gristDoc) {
|
|
||||||
Base.call(this, gristDoc);
|
|
||||||
this.replHist = gristDoc.docModel.replHist.createAllRowsModel("id");
|
|
||||||
this.docData = gristDoc.docData;
|
|
||||||
this.editingIndex = ko.observable(null);
|
|
||||||
this.histIndex = ko.observable(this.replHist.peekLength);
|
|
||||||
|
|
||||||
this.editorActive = ko.observable(false);
|
|
||||||
this.numLines = ko.observable(0);
|
|
||||||
this.row = null;
|
|
||||||
|
|
||||||
this._contentSizer = ko.observable('');
|
|
||||||
this._originalValue = '';
|
|
||||||
this._textInput = null;
|
|
||||||
|
|
||||||
this.commandGroup = this.autoDispose(commands.createGroup(
|
|
||||||
REPLTab.replCommands, this, this.editorActive));
|
|
||||||
}
|
|
||||||
|
|
||||||
Base.setBaseFor(REPLTab);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Editor commands for the cellEditor in the REPL Tab
|
|
||||||
* TODO: Using the command group, distinguish between "on enter" saves and "on blur" saves
|
|
||||||
* So that we can give up focus on blur
|
|
||||||
*/
|
|
||||||
REPLTab.replCommands = {
|
|
||||||
// TODO: GridView commands are activated more recently after startup.
|
|
||||||
fieldEditSave: function() {
|
|
||||||
if (!this._textInput || !this.editorActive() ||
|
|
||||||
!this._textInput.value.trim() && this.editingIndex() === NEW_LINE) { return; }
|
|
||||||
// TODO: Scroll pane does not automatically scroll down on save.
|
|
||||||
var self = this;
|
|
||||||
this.save()
|
|
||||||
.then(function(success) {
|
|
||||||
if (success) {
|
|
||||||
self.editingIndex(NEW_LINE);
|
|
||||||
self.clear();
|
|
||||||
// Refresh the history index.
|
|
||||||
self.histIndex(self.replHist.peekLength);
|
|
||||||
} else {
|
|
||||||
self.write("\n");
|
|
||||||
// Since focus is staying in the current input, increment lines.
|
|
||||||
self.numLines(self.numLines.peek()+1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fieldEditCancel: function() {
|
|
||||||
this.clear();
|
|
||||||
this.editingIndex(NEW_LINE);
|
|
||||||
},
|
|
||||||
nextField: function() {
|
|
||||||
// In this case, 'nextField' (Tab) inserts a tab.
|
|
||||||
this.write(INDENT_STR);
|
|
||||||
},
|
|
||||||
historyPrevious: function() {
|
|
||||||
// Fills the editor with the code previously entered.
|
|
||||||
if (this.editingIndex() === NEW_LINE) { this.writePrev(); }
|
|
||||||
},
|
|
||||||
historyNext: function() {
|
|
||||||
// Fills the editor with the code entered after the current code.
|
|
||||||
if (this.editingIndex() === NEW_LINE) { this.writeNext(); }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends the entered code as an EvalCode Useraction.
|
|
||||||
* @param {Function} callback - Is called with a single argument 'success' indicating
|
|
||||||
* whether the save was successful.
|
|
||||||
*/
|
|
||||||
REPLTab.prototype.save = function(callback) {
|
|
||||||
if (!this._textInput.value.trim()) {
|
|
||||||
// If its text is cleared, remove history item.
|
|
||||||
var currentEditIndex = this.editingIndex();
|
|
||||||
this.histIndex(this.replHist.peekLength - 1);
|
|
||||||
this.editorActive(false);
|
|
||||||
return this.docData.sendAction(["RemoveRecord", "_grist_REPL_Hist", currentEditIndex]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// If something is entered, save value.
|
|
||||||
var rowId = this.row ? this.row.id() : null;
|
|
||||||
return this.docData.sendAction(["EvalCode", this._textInput.value, rowId]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Builds object with REPLTab dom builder and settings for the sidepane.
|
|
||||||
REPLTab.prototype.buildConfigDomObj = function() {
|
|
||||||
return [{
|
|
||||||
'buildDom': this.buildDom.bind(this),
|
|
||||||
'keywords': ['repl', 'console', 'python', 'code', 'terminal']
|
|
||||||
}];
|
|
||||||
};
|
|
||||||
|
|
||||||
REPLTab.prototype.buildDom = function() {
|
|
||||||
var self = this;
|
|
||||||
return dom('div',
|
|
||||||
kd.foreach(this.replHist, function(replLine) {
|
|
||||||
return dom('div.repl-container',
|
|
||||||
dom('div.repl-text_line',
|
|
||||||
kd.scope(function() { return self.editingIndex() === replLine.id(); },
|
|
||||||
function(isEditing) {
|
|
||||||
if (isEditing) {
|
|
||||||
return dom('div.field.repl-field',
|
|
||||||
kd.scope(self.numLines, function(numLines) {
|
|
||||||
return self.buildPointerGroup(numLines);
|
|
||||||
}),
|
|
||||||
self.attachEditorDom(replLine));
|
|
||||||
} else {
|
|
||||||
var numLines = replLine.code().trim().split('\n').length;
|
|
||||||
return dom('div.repl-field',
|
|
||||||
dom.on('click', function() {
|
|
||||||
// TODO: Flickering occurs on click for multiline code segments.
|
|
||||||
self.editingIndex(replLine.id());
|
|
||||||
self.focus();
|
|
||||||
}),
|
|
||||||
self.buildPointerGroup(numLines),
|
|
||||||
dom('div.repl-text',
|
|
||||||
kd.text(replLine.code)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
dom('div.erase_line_button.unselectable', dom.on('click', function() {
|
|
||||||
self.histIndex(self.replHist.peekLength - 1);
|
|
||||||
return self.docData.sendAction(
|
|
||||||
["RemoveRecord", "_grist_REPL_Hist", replLine.id()]
|
|
||||||
);
|
|
||||||
}), '\u2A09'),
|
|
||||||
dom('div.re-eval_line_button.unselectable', dom.on('click', function() {
|
|
||||||
return self.docData.sendAction(
|
|
||||||
["EvalCode", replLine.code(), replLine.id()]
|
|
||||||
);
|
|
||||||
}), '\u27f3') // 'refresh' symbol
|
|
||||||
),
|
|
||||||
kd.maybe(replLine.outputText, function() {
|
|
||||||
return dom('div.repl-text.repl-output', kd.text(replLine.outputText));
|
|
||||||
}),
|
|
||||||
kd.maybe(replLine.errorText, function() {
|
|
||||||
return dom('div.repl-text.repl-error', kd.text(replLine.errorText));
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
// Special bottom editor which sends actions to add new records to the REPL hist.
|
|
||||||
dom('div.repl-newline',
|
|
||||||
dom.on('click', function() {
|
|
||||||
self.editingIndex(NEW_LINE);
|
|
||||||
self.focus();
|
|
||||||
}),
|
|
||||||
dom('div.field.repl-field',
|
|
||||||
kd.scope(self.numLines, function(numLines) {
|
|
||||||
return self.buildPointerGroup(self.editingIndex() === NEW_LINE ? numLines : 1);
|
|
||||||
}),
|
|
||||||
kd.maybe(ko.pureComputed(function() { return self.editingIndex() === NEW_LINE; }),
|
|
||||||
function() { return self.attachEditorDom(null); }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the set of pointers to the left of the code
|
|
||||||
* @param {String} code - The code for which the pointer group is to be built.
|
|
||||||
*/
|
|
||||||
REPLTab.prototype.buildPointerGroup = function(numLines) {
|
|
||||||
var pointers = [];
|
|
||||||
for (var i = 0; i < numLines; i++) {
|
|
||||||
pointers.push(dom('div.pointer', i ? '...' : '>>>'));
|
|
||||||
}
|
|
||||||
return dom('div.pointer_group.unselectable', pointers);
|
|
||||||
};
|
|
||||||
|
|
||||||
REPLTab.prototype.buildEditorDom = function() {
|
|
||||||
var self = this;
|
|
||||||
return dom('div.repl-cursor_editor',
|
|
||||||
dom('div.repl-content_measure.formula-text', kd.text(this._contentSizer)),
|
|
||||||
function() {
|
|
||||||
self._textInput = dom('textarea.repl-text_editor.formula-text',
|
|
||||||
kd.value(self.row ? self.row.code() : ""),
|
|
||||||
dom.on('focus', function() {
|
|
||||||
self.numLines(this.value.split('\n').length);
|
|
||||||
}),
|
|
||||||
dom.on('blur', function() {
|
|
||||||
if (!this._textInput || !this.editorActive()) { return; }
|
|
||||||
self.save()
|
|
||||||
.then(function(success) {
|
|
||||||
if (success) {
|
|
||||||
// If editing a new line, clear it to start fresh.
|
|
||||||
if (self.editingIndex() === NEW_LINE) { self.clear(); }
|
|
||||||
// Refresh the history index.
|
|
||||||
self.histIndex(self.replHist.peekLength);
|
|
||||||
} else {
|
|
||||||
self.write("\n");
|
|
||||||
}
|
|
||||||
self.editorActive(false);
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
//Resizes the textbox whenever user writes in it.
|
|
||||||
dom.on('input', function() {
|
|
||||||
self.numLines(this.value.split('\n').length);
|
|
||||||
self.resizeElem();
|
|
||||||
}),
|
|
||||||
dom.defer(function(elem) {
|
|
||||||
self.resizeElem();
|
|
||||||
elem.focus();
|
|
||||||
// Set the cursor at the end.
|
|
||||||
var elemLen = elem.value.length;
|
|
||||||
elem.selectionStart = elemLen;
|
|
||||||
elem.selectionEnd = elemLen;
|
|
||||||
}),
|
|
||||||
dom.on('mouseup mousedown click', function(event) { event.stopPropagation(); }),
|
|
||||||
self.commandGroup.attach()
|
|
||||||
);
|
|
||||||
return self._textInput;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function measures a hidden div with the same value as the textarea being edited and then resizes the textarea to match.
|
|
||||||
*/
|
|
||||||
REPLTab.prototype.resizeElem = function() {
|
|
||||||
// \u200B is a zero-width space; it is used so the textbox will expand vertically
|
|
||||||
// on newlines, but it does not add any width the string
|
|
||||||
this._contentSizer(this._textInput.value + '\u200B');
|
|
||||||
var rect = this._textInput.parentNode.childNodes[0].getBoundingClientRect();
|
|
||||||
//Allows form to expand passed its container div.
|
|
||||||
this._textInput.style.width = Math.ceil(rect.width) + 'px';
|
|
||||||
this._textInput.style.height = Math.ceil(rect.height) + 'px';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends text to the contents being edited
|
|
||||||
*/
|
|
||||||
REPLTab.prototype.write = function(text) {
|
|
||||||
this._textInput.value += text;
|
|
||||||
this.resizeElem();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears both the current text and any memory of text in the currently edited cell.
|
|
||||||
*/
|
|
||||||
REPLTab.prototype.clear = function() {
|
|
||||||
this._textInput.value = "";
|
|
||||||
this._orignalValue = "";
|
|
||||||
this.numLines(1);
|
|
||||||
this.resizeElem();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restores focus to the most recent input.
|
|
||||||
*/
|
|
||||||
REPLTab.prototype.focus = function() {
|
|
||||||
if (this._textInput) {
|
|
||||||
this._textInput.focus();
|
|
||||||
this.editorActive(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the code entered before the current code to the input.
|
|
||||||
*/
|
|
||||||
REPLTab.prototype.writePrev = function() {
|
|
||||||
this.histIndex(Math.max(this.histIndex.peek() - 1, 0));
|
|
||||||
this.clear();
|
|
||||||
if (this.replHist.at(this.histIndex.peek())) {
|
|
||||||
this.write(this.replHist.at(this.histIndex.peek()).code());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the code entered after the current code to the input.
|
|
||||||
*/
|
|
||||||
REPLTab.prototype.writeNext = function() {
|
|
||||||
this.histIndex(Math.min(this.histIndex() + 1, this.replHist.peekLength));
|
|
||||||
this.clear();
|
|
||||||
if (this.histIndex.peek() < this.replHist.peekLength) {
|
|
||||||
this.write(this.replHist.at(this.histIndex.peek()).code());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is called in the DOM element where an editor is desired.
|
|
||||||
* It attaches to as a child of that element with that elements value as default or whatever is set as an override value.
|
|
||||||
*/
|
|
||||||
REPLTab.prototype.attachEditorDom = function(row) {
|
|
||||||
var self = this;
|
|
||||||
self.row = row;
|
|
||||||
self._originalValue = self.row ? self.row.code() : "";
|
|
||||||
return self.buildEditorDom();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = REPLTab;
|
|
1
app/client/declarations.d.ts
vendored
1
app/client/declarations.d.ts
vendored
@ -6,7 +6,6 @@ declare module "app/client/components/DocConfigTab";
|
|||||||
declare module "app/client/components/GridView";
|
declare module "app/client/components/GridView";
|
||||||
declare module "app/client/components/Layout";
|
declare module "app/client/components/Layout";
|
||||||
declare module "app/client/components/LayoutEditor";
|
declare module "app/client/components/LayoutEditor";
|
||||||
declare module "app/client/components/REPLTab";
|
|
||||||
declare module "app/client/components/commandList";
|
declare module "app/client/components/commandList";
|
||||||
declare module "app/client/lib/Mousetrap";
|
declare module "app/client/lib/Mousetrap";
|
||||||
declare module "app/client/lib/browserGlobals";
|
declare module "app/client/lib/browserGlobals";
|
||||||
|
@ -28,7 +28,6 @@ import {ACLRuleRec, createACLRuleRec} from 'app/client/models/entities/ACLRuleRe
|
|||||||
import {ColumnRec, createColumnRec} from 'app/client/models/entities/ColumnRec';
|
import {ColumnRec, createColumnRec} from 'app/client/models/entities/ColumnRec';
|
||||||
import {createDocInfoRec, DocInfoRec} from 'app/client/models/entities/DocInfoRec';
|
import {createDocInfoRec, DocInfoRec} from 'app/client/models/entities/DocInfoRec';
|
||||||
import {createPageRec, PageRec} from 'app/client/models/entities/PageRec';
|
import {createPageRec, PageRec} from 'app/client/models/entities/PageRec';
|
||||||
import {createREPLRec, REPLRec} from 'app/client/models/entities/REPLRec';
|
|
||||||
import {createTabBarRec, TabBarRec} from 'app/client/models/entities/TabBarRec';
|
import {createTabBarRec, TabBarRec} from 'app/client/models/entities/TabBarRec';
|
||||||
import {createTableRec, TableRec} from 'app/client/models/entities/TableRec';
|
import {createTableRec, TableRec} from 'app/client/models/entities/TableRec';
|
||||||
import {createTableViewRec, TableViewRec} from 'app/client/models/entities/TableViewRec';
|
import {createTableViewRec, TableViewRec} from 'app/client/models/entities/TableViewRec';
|
||||||
@ -42,7 +41,6 @@ import {createViewSectionRec, ViewSectionRec} from 'app/client/models/entities/V
|
|||||||
export {ColumnRec} from 'app/client/models/entities/ColumnRec';
|
export {ColumnRec} from 'app/client/models/entities/ColumnRec';
|
||||||
export {DocInfoRec} from 'app/client/models/entities/DocInfoRec';
|
export {DocInfoRec} from 'app/client/models/entities/DocInfoRec';
|
||||||
export {PageRec} from 'app/client/models/entities/PageRec';
|
export {PageRec} from 'app/client/models/entities/PageRec';
|
||||||
export {REPLRec} from 'app/client/models/entities/REPLRec';
|
|
||||||
export {TabBarRec} from 'app/client/models/entities/TabBarRec';
|
export {TabBarRec} from 'app/client/models/entities/TabBarRec';
|
||||||
export {TableRec} from 'app/client/models/entities/TableRec';
|
export {TableRec} from 'app/client/models/entities/TableRec';
|
||||||
export {TableViewRec} from 'app/client/models/entities/TableViewRec';
|
export {TableViewRec} from 'app/client/models/entities/TableViewRec';
|
||||||
@ -110,7 +108,6 @@ export class DocModel {
|
|||||||
public tableViews: MTM<TableViewRec> = this._metaTableModel("_grist_TableViews", createTableViewRec);
|
public tableViews: MTM<TableViewRec> = this._metaTableModel("_grist_TableViews", createTableViewRec);
|
||||||
public tabBar: MTM<TabBarRec> = this._metaTableModel("_grist_TabBar", createTabBarRec);
|
public tabBar: MTM<TabBarRec> = this._metaTableModel("_grist_TabBar", createTabBarRec);
|
||||||
public validations: MTM<ValidationRec> = this._metaTableModel("_grist_Validations", createValidationRec);
|
public validations: MTM<ValidationRec> = this._metaTableModel("_grist_Validations", createValidationRec);
|
||||||
public replHist: MTM<REPLRec> = this._metaTableModel("_grist_REPL_Hist", createREPLRec);
|
|
||||||
public pages: MTM<PageRec> = this._metaTableModel("_grist_Pages", createPageRec);
|
public pages: MTM<PageRec> = this._metaTableModel("_grist_Pages", createPageRec);
|
||||||
public rules: MTM<ACLRuleRec> = this._metaTableModel("_grist_ACLRules", createACLRuleRec);
|
public rules: MTM<ACLRuleRec> = this._metaTableModel("_grist_ACLRules", createACLRuleRec);
|
||||||
|
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
import {DocModel, IRowModel} from 'app/client/models/DocModel';
|
|
||||||
|
|
||||||
// Record of input code and output text and error info for REPL.
|
|
||||||
export type REPLRec = IRowModel<"_grist_REPL_Hist">
|
|
||||||
|
|
||||||
export function createREPLRec(this: REPLRec, docModel: DocModel): void {
|
|
||||||
// no extra fields
|
|
||||||
}
|
|
@ -54,12 +54,6 @@ export function tools(owner: Disposable, gristDoc: GristDoc, leftPanelOpen: Obse
|
|||||||
cssPageLink(cssPageIcon('Validation'), cssLinkText('Validate Data'), testId('validate'),
|
cssPageLink(cssPageIcon('Validation'), cssLinkText('Validate Data'), testId('validate'),
|
||||||
dom.on('click', () => gristDoc.showTool('validations'))))
|
dom.on('click', () => gristDoc.showTool('validations'))))
|
||||||
),
|
),
|
||||||
// TODO: polish repl and add it back.
|
|
||||||
dom.maybe((use) => use(gristDoc.app.features).replTool, () =>
|
|
||||||
cssPageEntry(
|
|
||||||
cssPageLink(cssPageIcon('Repl'), cssLinkText('REPL'), testId('repl'),
|
|
||||||
dom.on('click', () => gristDoc.showTool('repl'))))
|
|
||||||
),
|
|
||||||
cssPageEntry(
|
cssPageEntry(
|
||||||
cssPageEntry.cls('-selected', (use) => use(gristDoc.activeViewId) === 'code'),
|
cssPageEntry.cls('-selected', (use) => use(gristDoc.activeViewId) === 'code'),
|
||||||
cssPageLink(cssPageIcon('Code'),
|
cssPageLink(cssPageIcon('Code'),
|
||||||
|
@ -25,6 +25,5 @@ export interface ISupportedFeatures {
|
|||||||
// Plugin views, REPL, and Validations all need work, but are exposed here to allow existing
|
// Plugin views, REPL, and Validations all need work, but are exposed here to allow existing
|
||||||
// tests to continue running. These only affect client-side code.
|
// tests to continue running. These only affect client-side code.
|
||||||
customViewPlugin?: boolean;
|
customViewPlugin?: boolean;
|
||||||
replTool?: boolean;
|
|
||||||
validationsTool?: boolean;
|
validationsTool?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,6 @@ import table as table_module
|
|||||||
from user import User # pylint:disable=wrong-import-order
|
from user import User # pylint:disable=wrong-import-order
|
||||||
import useractions
|
import useractions
|
||||||
import column
|
import column
|
||||||
import repl
|
|
||||||
import urllib_patch # noqa imported for side effect # pylint:disable=unused-import
|
import urllib_patch # noqa imported for side effect # pylint:disable=unused-import
|
||||||
|
|
||||||
log = logger.Logger(__name__, logger.INFO)
|
log = logger.Logger(__name__, logger.INFO)
|
||||||
@ -232,9 +231,6 @@ class Engine(object):
|
|||||||
# A flag for when a useraction causes a schema change, to verify consistency afterwards.
|
# A flag for when a useraction causes a schema change, to verify consistency afterwards.
|
||||||
self._schema_updated = False
|
self._schema_updated = False
|
||||||
|
|
||||||
# Locals dict for recently executed code in the REPL
|
|
||||||
self._repl = repl.REPLInterpreter()
|
|
||||||
|
|
||||||
# Stores an exception representing the first unevaluated cell met while recomputing the
|
# Stores an exception representing the first unevaluated cell met while recomputing the
|
||||||
# current cell.
|
# current cell.
|
||||||
self._cell_required_error = None
|
self._cell_required_error = None
|
||||||
@ -976,11 +972,6 @@ class Engine(object):
|
|||||||
for (col_obj, values) in cols}
|
for (col_obj, values) in cols}
|
||||||
)
|
)
|
||||||
|
|
||||||
def eval_user_code(self, src):
|
|
||||||
ret = self._repl.runsource(src)
|
|
||||||
self.gencode.usercode.__dict__.update(self._repl.locals)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def invalidate_records(self, table_id, row_ids=depend.ALL_ROWS, col_ids=None,
|
def invalidate_records(self, table_id, row_ids=depend.ALL_ROWS, col_ids=None,
|
||||||
data_cols_to_recompute=frozenset()):
|
data_cols_to_recompute=frozenset()):
|
||||||
"""
|
"""
|
||||||
@ -1017,8 +1008,6 @@ class Engine(object):
|
|||||||
def rebuild_usercode(self):
|
def rebuild_usercode(self):
|
||||||
"""
|
"""
|
||||||
Compiles the usercode from the schema, and updates all tables and columns to match.
|
Compiles the usercode from the schema, and updates all tables and columns to match.
|
||||||
Also, keeps the locals in the repl in sync with the user code, so that the repl has access to
|
|
||||||
usercode and vice-versa.
|
|
||||||
"""
|
"""
|
||||||
self.gencode.make_module(self.schema)
|
self.gencode.make_module(self.schema)
|
||||||
|
|
||||||
@ -1040,7 +1029,6 @@ class Engine(object):
|
|||||||
for table_id, table in six.iteritems(old_tables):
|
for table_id, table in six.iteritems(old_tables):
|
||||||
if table_id not in self.tables:
|
if table_id not in self.tables:
|
||||||
self._update_table_model(table, None)
|
self._update_table_model(table, None)
|
||||||
self._repl.locals.pop(table_id, None)
|
|
||||||
|
|
||||||
# Update docmodel with references to the updated metadata tables.
|
# Update docmodel with references to the updated metadata tables.
|
||||||
self.docmodel.update_tables()
|
self.docmodel.update_tables()
|
||||||
@ -1048,11 +1036,6 @@ class Engine(object):
|
|||||||
# Set flag to rebuild dependencies of trigger columns after any potential renames, etc.
|
# Set flag to rebuild dependencies of trigger columns after any potential renames, etc.
|
||||||
self.trigger_columns_changed()
|
self.trigger_columns_changed()
|
||||||
|
|
||||||
# The order here is important to make sure that when we update the usercode,
|
|
||||||
# we don't overwrite with outdated usercode entries
|
|
||||||
self._repl.locals.update(self.gencode.usercode.__dict__)
|
|
||||||
self.gencode.usercode.__dict__.update(self._repl.locals)
|
|
||||||
|
|
||||||
# Update the context used for autocompletions.
|
# Update the context used for autocompletions.
|
||||||
self._autocomplete_context = AutocompleteContext(self.gencode.usercode.__dict__)
|
self._autocomplete_context = AutocompleteContext(self.gencode.usercode.__dict__)
|
||||||
|
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
"""
|
|
||||||
This module implements an interpreter for a REPL. It subclasses Python's
|
|
||||||
code.InteractiveInterpreter class, implementing most of its methods, but with
|
|
||||||
slight changes in order to be convenient for Grist's purposes
|
|
||||||
"""
|
|
||||||
|
|
||||||
import code
|
|
||||||
import sys
|
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
SUCCESS = 0
|
|
||||||
INCOMPLETE = 1
|
|
||||||
ERROR = 2
|
|
||||||
|
|
||||||
EvalTuple = namedtuple("EvalTuple", ("output", "error", "status"))
|
|
||||||
|
|
||||||
#pylint: disable=exec-used, bare-except
|
|
||||||
class REPLInterpreter(code.InteractiveInterpreter):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
code.InteractiveInterpreter.__init__(self)
|
|
||||||
self.error_text = ""
|
|
||||||
|
|
||||||
def runsource(self, source, filename="<input>", symbol="single"):
|
|
||||||
"""
|
|
||||||
Compiles and executes source. Returns an EvalTuple with a status
|
|
||||||
INCOMPLETE if the code is incomplete,
|
|
||||||
ERROR if it encountered a compilation or run-time error,
|
|
||||||
SUCCESS otherwise.
|
|
||||||
|
|
||||||
an output, which gives all of the output of the user's program
|
|
||||||
(with stderr piped to stdout, essentially, though mock-file objects are used)
|
|
||||||
|
|
||||||
an error, which reports a syntax error at compilation or a runtime error with a
|
|
||||||
Traceback.
|
|
||||||
"""
|
|
||||||
old_stdout = sys.stdout
|
|
||||||
old_stderr = sys.stderr
|
|
||||||
|
|
||||||
user_output = six.StringIO()
|
|
||||||
|
|
||||||
self.error_text = ""
|
|
||||||
try:
|
|
||||||
code = self.compile(source, filename, symbol)
|
|
||||||
except (OverflowError, SyntaxError, ValueError):
|
|
||||||
self.showsyntaxerror(filename)
|
|
||||||
status = ERROR
|
|
||||||
else:
|
|
||||||
status = INCOMPLETE if code is None else SUCCESS
|
|
||||||
|
|
||||||
if status == SUCCESS:
|
|
||||||
|
|
||||||
try:
|
|
||||||
# We use temproray variables to access stdio/stdout
|
|
||||||
# to make sure the client can't do funky things
|
|
||||||
# like get/set attr and have that hurt us
|
|
||||||
sys.stdout = user_output
|
|
||||||
sys.stderr = user_output
|
|
||||||
exec(code, self.locals)
|
|
||||||
except:
|
|
||||||
# bare except to catch absolutely all things the user can throw
|
|
||||||
self.showtraceback()
|
|
||||||
status = ERROR
|
|
||||||
finally:
|
|
||||||
sys.stdout = old_stdout
|
|
||||||
sys.stderr = old_stderr
|
|
||||||
|
|
||||||
program_output = user_output.getvalue()
|
|
||||||
try:
|
|
||||||
user_output.close()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return EvalTuple(program_output, self.error_text, status)
|
|
||||||
|
|
||||||
def write(self, txt):
|
|
||||||
"""
|
|
||||||
Used by showsyntaxerror and showtraceback
|
|
||||||
"""
|
|
||||||
self.error_text += txt
|
|
||||||
|
|
||||||
def runcode(self, code):
|
|
||||||
"""
|
|
||||||
This would normally do the part of runsource after compiling the code, but doesn't quite
|
|
||||||
make sense as its own function for our purposes because it couldn't support an INCOMPLETE
|
|
||||||
return value, etc. We explicitly hide it here to make sure the base class's version isn't
|
|
||||||
called by accident.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError("REPLInterpreter.runcode not implemented, use runsource instead")
|
|
@ -2709,167 +2709,6 @@
|
|||||||
}]
|
}]
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
|
|
||||||
//------------------------------------------------------------------------
|
|
||||||
"TEST_CASE" : "eval_code",
|
|
||||||
//------------------------------------------------------------------------
|
|
||||||
"BODY": [
|
|
||||||
["LOAD_SAMPLE", "basic"],
|
|
||||||
|
|
||||||
["APPLY", {
|
|
||||||
"USER_ACTIONS": [
|
|
||||||
// Basic tests
|
|
||||||
["EvalCode", "print('cats')", null],
|
|
||||||
["EvalCode", "2 * 3 - 1 // 7 + 4", null],
|
|
||||||
// Exception
|
|
||||||
["EvalCode", "raise Exception('everything broke')", null],
|
|
||||||
// Incomplete structure
|
|
||||||
["EvalCode", "for x in range(1, 100):", null],
|
|
||||||
// Re-evaluation
|
|
||||||
["EvalCode", "print('cats')", 1],
|
|
||||||
["EvalCode", "print('dogs')", 1],
|
|
||||||
// Function definition
|
|
||||||
["EvalCode", "def foo(x):\n\treturn x * 10\n", null],
|
|
||||||
["EvalCode", "foo(10)", null]
|
|
||||||
],
|
|
||||||
"ACTIONS" : {
|
|
||||||
"stored" : [
|
|
||||||
["AddRecord", "_grist_REPL_Hist", 1,
|
|
||||||
{"code": "print('cats')", "errorText": "", "outputText": "cats\n"}],
|
|
||||||
["AddRecord", "_grist_REPL_Hist", 2,
|
|
||||||
{"code": "2 * 3 - 1 // 7 + 4", "errorText": "", "outputText": "10\n"}],
|
|
||||||
["AddRecord", "_grist_REPL_Hist", 3,
|
|
||||||
{"code": "raise Exception('everything broke')", "errorText": "Traceback (most recent call last):\n File \"<input>\", line 1, in <module>\nException: everything broke\n", "outputText": ""}],
|
|
||||||
["UpdateRecord", "_grist_REPL_Hist", 1,
|
|
||||||
{"code": "print('cats')", "errorText": "", "outputText": "cats\n"}],
|
|
||||||
["UpdateRecord", "_grist_REPL_Hist", 1,
|
|
||||||
{"code": "print('dogs')", "errorText": "", "outputText": "dogs\n"}],
|
|
||||||
["AddRecord", "_grist_REPL_Hist", 4,
|
|
||||||
{"code": "def foo(x):\n\treturn x * 10\n", "errorText": "", "outputText": ""}],
|
|
||||||
["AddRecord", "_grist_REPL_Hist", 5,
|
|
||||||
{"code": "foo(10)", "errorText": "", "outputText": "100\n"}]
|
|
||||||
],
|
|
||||||
"direct": [true, true, true, true, true, true, true],
|
|
||||||
"undo" : [
|
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 1],
|
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 2],
|
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 3],
|
|
||||||
["UpdateRecord", "_grist_REPL_Hist", 1,
|
|
||||||
{"code": "print('cats')", "errorText": "", "outputText": "cats\n"}],
|
|
||||||
["UpdateRecord", "_grist_REPL_Hist", 1,
|
|
||||||
{"code": "print('cats')", "errorText": "", "outputText": "cats\n"}],
|
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 4],
|
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 5]
|
|
||||||
],
|
|
||||||
"retValue" : [true,true,true,false,true,true,true,true]
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
|
|
||||||
["APPLY", {
|
|
||||||
"USER_ACTIONS": [
|
|
||||||
// Access to usercode before and after re-generation
|
|
||||||
["EvalCode", "list(map(str, Students.all.firstName))", null],
|
|
||||||
["UpdateRecord", "Students", 1, {"firstName": "2e6"}],
|
|
||||||
["ModifyColumn", "Students", "firstName", { "type" : "Numeric" }],
|
|
||||||
["EvalCode", "list(Students.all.firstName)", 6],
|
|
||||||
// Make sure that other tables are still usable as well after a schema change.
|
|
||||||
["EvalCode", "len(Schools.all)", 6]
|
|
||||||
],
|
|
||||||
"ACTIONS": {
|
|
||||||
"stored": [
|
|
||||||
["AddRecord", "_grist_REPL_Hist", 6,
|
|
||||||
{"code": "list(map(str, Students.all.firstName))", "errorText": "", "outputText": "['Barack', 'George W', 'Bill', 'George H', 'Ronald', 'Jimmy', 'Gerald']\n"}],
|
|
||||||
|
|
||||||
["UpdateRecord", "Students", 1, {"firstName": "2e6"}],
|
|
||||||
["ModifyColumn", "Students", "firstName", {"type": "Numeric"}],
|
|
||||||
["UpdateRecord", "Students", 1, {"firstName": 2e6}],
|
|
||||||
["UpdateRecord", "_grist_Tables_column", 1, {"type": "Numeric"}],
|
|
||||||
|
|
||||||
["UpdateRecord", "_grist_REPL_Hist", 6, {"code": "list(Students.all.firstName)",
|
|
||||||
"errorText": "",
|
|
||||||
"outputText": "[2000000.0, AltText('George W'), AltText('Bill'), AltText('George H'), AltText('Ronald'), AltText('Jimmy'), AltText('Gerald')]\n"}],
|
|
||||||
["UpdateRecord", "_grist_REPL_Hist", 6,
|
|
||||||
{"code": "len(Schools.all)", "errorText": "", "outputText": "6\n"}],
|
|
||||||
["BulkUpdateRecord", "Students", [1, 2, 3, 4, 5, 6, 8], {
|
|
||||||
"fullName": [["E", "TypeError"], ["E","TypeError"], ["E","TypeError"],
|
|
||||||
["E","TypeError"], ["E","TypeError"], ["E","TypeError"], ["E","TypeError"]]
|
|
||||||
}],
|
|
||||||
["BulkUpdateRecord", "Students", [1, 2, 3, 4, 5, 6, 8], {
|
|
||||||
"fullNameLen": [["E","TypeError"], ["E","TypeError"], ["E","TypeError"],
|
|
||||||
["E","TypeError"], ["E","TypeError"], ["E","TypeError"], ["E","TypeError"]]
|
|
||||||
}]
|
|
||||||
],
|
|
||||||
"direct": [true, true, true, false, true, true, true, false, false],
|
|
||||||
"undo": [
|
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 6],
|
|
||||||
|
|
||||||
["UpdateRecord", "Students", 1, {"firstName": "Barack"}],
|
|
||||||
["UpdateRecord", "Students", 1, {"firstName": "2e6"}],
|
|
||||||
["ModifyColumn", "Students", "firstName", {"type": "Text"}],
|
|
||||||
["UpdateRecord", "_grist_Tables_column", 1, {"type": "Text"}],
|
|
||||||
|
|
||||||
["UpdateRecord", "_grist_REPL_Hist", 6, {"code": "list(map(str, Students.all.firstName))",
|
|
||||||
"errorText": "", "outputText": "['Barack', 'George W', 'Bill', 'George H', 'Ronald', 'Jimmy', 'Gerald']\n"}],
|
|
||||||
["UpdateRecord", "_grist_REPL_Hist", 6, {"code": "list(Students.all.firstName)",
|
|
||||||
"errorText": "",
|
|
||||||
"outputText": "[2000000.0, AltText('George W'), AltText('Bill'), AltText('George H'), AltText('Ronald'), AltText('Jimmy'), AltText('Gerald')]\n"}],
|
|
||||||
["BulkUpdateRecord", "Students", [1, 2, 3, 4, 5, 6, 8],
|
|
||||||
{"fullName": ["Barack Obama", "George W Bush", "Bill Clinton", "George H Bush", "Ronald Reagan", "Jimmy Carter", "Gerald Ford"]}],
|
|
||||||
["BulkUpdateRecord", "Students", [1, 2, 3, 4, 5, 6, 8],
|
|
||||||
{"fullNameLen": [12, 13, 12, 13, 13, 12, 11]}]
|
|
||||||
],
|
|
||||||
"retValue": [true,null,null,true,true]
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
|
|
||||||
["APPLY", {
|
|
||||||
"USER_ACTIONS": [
|
|
||||||
// Syntax Error
|
|
||||||
["EvalCode", "not correct c", null],
|
|
||||||
// Other continuations
|
|
||||||
["EvalCode", "map(filter, ", null],
|
|
||||||
["EvalCode", "[1,2,3,", null],
|
|
||||||
// sys.exit
|
|
||||||
["EvalCode", "import sys", null],
|
|
||||||
["EvalCode", "sys.exit(0)", null],
|
|
||||||
// User reassignment of sys.stdout/sys.stderr
|
|
||||||
["EvalCode", "sys.stdout = None", null],
|
|
||||||
["EvalCode", "setattr(sys.stdout, 'getvalue', lambda : 2)", null],
|
|
||||||
["EvalCode", "def foo():\n global stdout\n exec('stdout = 2')\n", null],
|
|
||||||
["EvalCode", "setattr(sys.stderr, 'close', foo)", null]
|
|
||||||
],
|
|
||||||
"ACTIONS": {
|
|
||||||
"stored": [
|
|
||||||
["AddRecord", "_grist_REPL_Hist", 7,
|
|
||||||
{"code": "not correct c", "errorText": " File \"<input>\", line 1\n not correct c\n ^\nSyntaxError: invalid syntax\n", "outputText": ""}],
|
|
||||||
["AddRecord", "_grist_REPL_Hist", 8,
|
|
||||||
{"code": "import sys", "errorText": "", "outputText": ""}],
|
|
||||||
["AddRecord", "_grist_REPL_Hist", 9,
|
|
||||||
{"code": "sys.exit(0)", "errorText": "Traceback (most recent call last):\n File \"<input>\", line 1, in <module>\nSystemExit: 0\n", "outputText": ""}],
|
|
||||||
["AddRecord", "_grist_REPL_Hist", 10,
|
|
||||||
{"code": "sys.stdout = None", "errorText": "", "outputText": ""}],
|
|
||||||
["AddRecord", "_grist_REPL_Hist", 11,
|
|
||||||
{"code": "setattr(sys.stdout, 'getvalue', lambda : 2)", "errorText": "", "outputText": 2}],
|
|
||||||
["AddRecord", "_grist_REPL_Hist", 12,
|
|
||||||
{"code": "def foo():\n global stdout\n exec('stdout = 2')\n", "errorText": "", "outputText": ""}],
|
|
||||||
["AddRecord", "_grist_REPL_Hist", 13,
|
|
||||||
{"code": "setattr(sys.stderr, 'close', foo)", "errorText": "", "outputText": ""}]
|
|
||||||
],
|
|
||||||
"direct": [true, true, true, true, true, true, true],
|
|
||||||
"undo": [
|
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 7],
|
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 8],
|
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 9],
|
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 10],
|
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 11],
|
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 12],
|
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 13]
|
|
||||||
],
|
|
||||||
"retValue" : [true,false,false,true,true,true,true,true,true ]
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
//------------------------------------------------------------------------
|
//------------------------------------------------------------------------
|
||||||
"TEST_CASE" : "reserved_keywords",
|
"TEST_CASE" : "reserved_keywords",
|
||||||
//------------------------------------------------------------------------
|
//------------------------------------------------------------------------
|
||||||
|
@ -17,7 +17,6 @@ import schema
|
|||||||
from schema import RecalcWhen
|
from schema import RecalcWhen
|
||||||
import summary
|
import summary
|
||||||
import import_actions
|
import import_actions
|
||||||
import repl
|
|
||||||
import textbuilder
|
import textbuilder
|
||||||
import usertypes
|
import usertypes
|
||||||
import treeview
|
import treeview
|
||||||
@ -239,37 +238,6 @@ class UserActions(object):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
#--------------------------------------
|
|
||||||
# User Actions on usercode
|
|
||||||
#--------------------------------------
|
|
||||||
|
|
||||||
@useraction
|
|
||||||
def EvalCode(self, code, row_id):
|
|
||||||
"""
|
|
||||||
Evaluates code in the REPL.
|
|
||||||
a return value of false indicates that the user's code was incomplete
|
|
||||||
(and in this case, we do not create any docactions)
|
|
||||||
otherwise, we either add a new record to the REPL_hist (row_id=null) or Update an existing
|
|
||||||
record (row_id=num) with the results of evaluating the code.
|
|
||||||
"""
|
|
||||||
evaluation = self._engine.eval_user_code(code)
|
|
||||||
if evaluation.status == repl.INCOMPLETE:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
hist = self._engine.tables["_grist_REPL_Hist"]
|
|
||||||
record = { "code" : code, "outputText" : evaluation.output, "errorText" : evaluation.error }
|
|
||||||
if row_id is None:
|
|
||||||
# This is a new evaluation, append it to the REPL history
|
|
||||||
action = actions.AddRecord(hist.table_id, hist.next_row_id(), record)
|
|
||||||
else:
|
|
||||||
# This is a re-evaluation, update the old retValue
|
|
||||||
action = actions.UpdateRecord(hist.table_id, row_id, record)
|
|
||||||
|
|
||||||
self._do_doc_action(action)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
#----------------------------------------
|
#----------------------------------------
|
||||||
# User actions on records.
|
# User actions on records.
|
||||||
#----------------------------------------
|
#----------------------------------------
|
||||||
|
Loading…
Reference in New Issue
Block a user