mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +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