kopia lustrzana https://github.com/c9/core
921 wiersze
30 KiB
JavaScript
921 wiersze
30 KiB
JavaScript
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Distributed under the BSD license:
|
|
*
|
|
* Copyright (c) 2010, Ajax.org B.V.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of Ajax.org B.V. nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
|
|
define(function(require, exports, module) {
|
|
"use strict";
|
|
|
|
var dom = require("ace/lib/dom");
|
|
var Anchor = require("ace/anchor").Anchor;
|
|
|
|
var HashHandler = require("ace/keyboard/hash_handler").HashHandler;
|
|
var Range = require("ace/range").Range;
|
|
var comparePoints = Range.comparePoints;
|
|
|
|
var ReplCell = require("./repl_cell").ReplCell;
|
|
|
|
var css = require("ace/requirejs/text!./repl.css");
|
|
dom.importCssString(css, "ace_repl");
|
|
|
|
|
|
var replCommands = new HashHandler([{
|
|
name: "newLine",
|
|
bindKey: { win: "Shift-Return|Alt-Enter", mac: "Shift-Return|Alt-Enter" },
|
|
exec: function(editor) {editor.insert("\n");},
|
|
scrollIntoView: "cursor"
|
|
}, {
|
|
name: "eval",
|
|
bindKey: "Ctrl-Return|Cmd-Return",
|
|
exec: function(editor) {return editor.repl.eval(true);},
|
|
scrollIntoView: "cursor"
|
|
}, {
|
|
name: "evalOrNewLine",
|
|
bindKey: "Return",
|
|
exec: function(editor) {return editor.repl.eval();},
|
|
scrollIntoView: "cursor"
|
|
}, {
|
|
name: "down",
|
|
bindKey: "down",
|
|
exec: function(editor) {return editor.repl.navigateHistory(1);},
|
|
scrollIntoView: "center-animate"
|
|
}, {
|
|
name: "up",
|
|
bindKey: "up",
|
|
exec: function(editor) {return editor.repl.navigateHistory(-1);},
|
|
scrollIntoView: "center-animate"
|
|
}, {
|
|
name: "prevCell",
|
|
bindKey: { mac: "cmd-up", win: "ctrl-up" },
|
|
exec: function(editor) {return editor.repl.moveByCells(-1, null, "input");},
|
|
scrollIntoView: "center-animate"
|
|
}, {
|
|
name: "nextCell",
|
|
bindKey: { mac: "cmd-down", win: "ctrl-down" },
|
|
exec: function(editor) {return editor.repl.moveByCells(1, null, "input");},
|
|
scrollIntoView: "center-animate"
|
|
}, {
|
|
name: "firstCell",
|
|
bindKey: { mac: "alt-up|ctrl-home", win: "ctrl-home" },
|
|
exec: function(editor) {return editor.repl.moveByCells("first", null, "input");},
|
|
scrollIntoView: "center-animate"
|
|
}, {
|
|
name: "lastCell",
|
|
bindKey: { mac: "alt-down|ctrl-end", win: "ctrl-end" },
|
|
exec: function(editor) {return editor.repl.moveByCells("last", null, "input");},
|
|
scrollIntoView: "center-animate"
|
|
}, {
|
|
name: "clear",
|
|
bindKey: { mac: "cmd-k", win: "Alt-k" },
|
|
exec: function(editor) {return editor.repl.clear();},
|
|
scrollIntoView: "center-animate"
|
|
}, {
|
|
name: "removeOutputCell",
|
|
bindKey: { mac: "Shift-delete", win: "Shift-delete" },
|
|
exec: function(editor) {return editor.repl.removeOutputCell();},
|
|
scrollIntoView: "cursor"
|
|
}, {
|
|
name: "newInputCell",
|
|
bindKey: { mac: "ctrl-insert", win: "ctrl-insert" },
|
|
exec: function(editor) { editor.repl.insertCell();},
|
|
scrollIntoView: "center-animate"
|
|
}]);
|
|
|
|
|
|
for (var key in replCommands.commands) {
|
|
replCommands.commands[key].isRepl = true;
|
|
}
|
|
|
|
|
|
/******************************************************/
|
|
var Repl = function(session, options) {
|
|
options = options || {};
|
|
this.history = new History();
|
|
this.evaluator = options.evaluator || new Evaluator();
|
|
this.session = session;
|
|
|
|
this._replResize = this._replResize.bind(this);
|
|
this.updateCellsOnChange = this.updateCellsOnChange.bind(this);
|
|
this.updateWidgets = this.updateWidgets.bind(this);
|
|
this.measureWidgets = this.measureWidgets.bind(this);
|
|
this.session._changedWidgets = [];
|
|
this.detach = this.detach.bind(this);
|
|
|
|
this.session.on("change", this.updateCellsOnChange);
|
|
|
|
session.repl = this;
|
|
|
|
session.replCells = [];
|
|
var pos = { row: session.getLength(), column: 0 };
|
|
if (!session.getValue() && options.message)
|
|
pos = session.insert(pos, options.message);
|
|
if (session.getValue())
|
|
this.insertCell({ row: 0, column: 0 }, { type: "start" }, true);
|
|
var last = this.insertCell(pos, { type: "input" });
|
|
this.select(last.range.end);
|
|
|
|
|
|
this.session.on("changeMode", function() {
|
|
session.getFoldWidget = function(row) {
|
|
if (!session.replCells[row])
|
|
return;
|
|
if (session.replCells[row + 1])
|
|
return;
|
|
if (row == session.replCells.length - 1)
|
|
return;
|
|
if (session.replCells[row].lineWidget)
|
|
return;
|
|
return "start";
|
|
};
|
|
session.getFoldWidgetRange = function(row) {
|
|
return session.repl.getCellAt(row).range;
|
|
};
|
|
session.bgTokenizer.$tokenizeRow = session.repl.$tokenizeRow;
|
|
session.bgTokenizer.session = session;
|
|
});
|
|
|
|
session.getRowLength = function(row) {
|
|
if (this.lineWidgets)
|
|
var h = this.lineWidgets[row] && this.lineWidgets[row].rowCount || 0;
|
|
else
|
|
h = 0;
|
|
if (!this.$useWrapMode || !this.$wrapData[row]) {
|
|
return 1 + h;
|
|
} else {
|
|
return this.$wrapData[row].length + 1 + h;
|
|
}
|
|
};
|
|
|
|
|
|
session.$getWidgetScreenLength = function() {
|
|
var screenRows = 0;
|
|
this.lineWidgets.forEach(function(w) {
|
|
if (w && w.rowCount)
|
|
screenRows += w.rowCount;
|
|
});
|
|
return screenRows;
|
|
};
|
|
|
|
session.gutterRenderer = this.gutterRenderer;
|
|
|
|
this.session.setMode(options.mode);
|
|
|
|
this.$updateSession();
|
|
this._addCursorMonitor();
|
|
};
|
|
|
|
(function() {
|
|
this.message = "\nWelcome to ace repl!\n";
|
|
|
|
this.setEvaluator = function(evaluator) {
|
|
this.evaluator = evaluator;
|
|
};
|
|
this.getEvaluator = function() {
|
|
return this.evaluator;
|
|
};
|
|
|
|
this._replResize = function(oldSize, renderer) {
|
|
var session = renderer.session;
|
|
var size = renderer.$size;
|
|
var dh = size.scrollerHeight - oldSize.scrollerHeight;
|
|
if (dh > 0)
|
|
return;
|
|
|
|
var oldMaxScrollTop = renderer.layerConfig.maxHeight
|
|
- oldSize.scrollerHeight + renderer.scrollMargin.v;
|
|
var scrollTop = session.getScrollTop();
|
|
if (scrollTop > oldMaxScrollTop - renderer.layerConfig.lineHeight) {
|
|
session.setScrollTop(scrollTop - dh);
|
|
}
|
|
};
|
|
|
|
this.gutterRenderer = {
|
|
getText: function(session, row) {
|
|
var cell = session.replCells[row];
|
|
if (cell)
|
|
return cell.prompt || "";
|
|
return row + "";
|
|
},
|
|
getWidth: function(session, lastLineNumber, config) {
|
|
var chars = Math.max(lastLineNumber.toString().length, session.maxPromptLength || 0);
|
|
return chars * config.characterWidth;
|
|
}
|
|
};
|
|
|
|
// hide cursor in non editable cells
|
|
this._addCursorMonitor = function() {
|
|
this._updateCursorVisibility = this._updateCursorVisibility.bind(this);
|
|
this.$cursorChanged = false;
|
|
var markDirty = function() {
|
|
this.$cursorChanged = true;
|
|
}.bind(this);
|
|
this.session.selection.on("changeSelection", markDirty);
|
|
this.session.selection.on("changeCursor", markDirty);
|
|
};
|
|
|
|
this._updateCursorVisibility = function(e, renderer) {
|
|
if (!this.$cursorChanged) return;
|
|
this.$cursorChanged = false;
|
|
var cell = this.getCurrentCell();
|
|
var visible = cell && cell.type === "input";
|
|
if (visible != renderer.$cursorLayer.inEditableCell) {
|
|
renderer.$cursorLayer.inEditableCell = visible;
|
|
renderer.$cursorLayer.element.style.opacity = visible ? "" : "0";
|
|
}
|
|
};
|
|
|
|
this.onMouseUp = function(e) {
|
|
var editor = e.editor;
|
|
if (editor.repl.$mouseTimer)
|
|
clearTimeout(editor.repl.$mouseTimer);
|
|
editor.repl.$mouseTimer = setTimeout(function() {
|
|
var sel = editor.selection;
|
|
if (sel.isEmpty() && !sel.rangeCount) {
|
|
var cell = editor.repl.getCurrentCell();
|
|
if (cell.type != "input") {
|
|
var r = cell.getRange();
|
|
if (r && (sel.lead.row - r.start.row) / (r.end.row - r.start.row) < 0.1)
|
|
cell = editor.repl.getSiblingCell(-1, cell, "input");
|
|
if (!cell)
|
|
cell = editor.repl.getSiblingCell(1, cell, "input");
|
|
if (cell)
|
|
editor.repl.select(cell.range.end);
|
|
}
|
|
}
|
|
}, 250);
|
|
};
|
|
|
|
// commands
|
|
this.beforeCommand = function(e) {
|
|
var editor = e.editor;
|
|
var command = e.command;
|
|
var cell = editor.repl.getCurrentCell();
|
|
if (!editor.curReplOp)
|
|
editor.curReplOp = { command: command, cell: cell };
|
|
|
|
if (command.isRepl)
|
|
return;
|
|
if (cell && cell.lineWidget) {
|
|
editor.repl.moveByCells(-1, cell);
|
|
e.preventDefault();
|
|
}
|
|
|
|
if (cell && cell.type != "input") {
|
|
if (!command.readOnly)
|
|
e.preventDefault();
|
|
return;
|
|
}
|
|
|
|
if (!cell)
|
|
return;
|
|
|
|
var op = editor.curReplOp;
|
|
|
|
if (command.readOnly) {
|
|
op.clipSelection = "before";
|
|
if (editor.lastReplOp && editor.lastReplOp.command === command) {
|
|
// todo allow some commands to go outside of cell
|
|
op.clipSelection = "after";
|
|
}
|
|
} else if (command) {
|
|
op.clipSelection = "before";
|
|
}
|
|
|
|
if (op.clipSelection == "before") {
|
|
var range = cell.getRange();
|
|
editor.repl.$trackedRange = range;
|
|
setClipToRange(editor.selection.lead, range);
|
|
setClipToRange(editor.selection.anchor, range);
|
|
}
|
|
};
|
|
|
|
this.afterCommand = function(e) {
|
|
var editor = e.editor;
|
|
var op = editor.curReplOp;
|
|
editor.curReplOp = null;
|
|
editor.lastReplOp = op;
|
|
if (!op)
|
|
return;
|
|
var command = op.command;
|
|
if (op.clipSelection == "before") {
|
|
setClipToRange(editor.selection.lead, false);
|
|
setClipToRange(editor.selection.anchor, false);
|
|
} else if (op.clipSelection == "after") {
|
|
var range = editor.selection.toOrientedRange();
|
|
if (op.cell) {
|
|
range.clip(op.cell.getRange());
|
|
editor.selection.fromOrientedRange(range);
|
|
}
|
|
}
|
|
editor.repl.$trackedRange = null;
|
|
if (!command.readOnly && !command.isRepl)
|
|
editor.repl.ensureLastInputCell();
|
|
};
|
|
|
|
this.attach = function(editor) {
|
|
if (editor.repl && editor.repl != this)
|
|
editor.repl.detach();
|
|
|
|
if (this.editor == editor)
|
|
return;
|
|
|
|
this.detach();
|
|
this.editor = editor;
|
|
|
|
this.editor.on("changeSession", this.detach);
|
|
|
|
editor.keyBinding.addKeyboardHandler(replCommands);
|
|
editor.commands.on("exec", this.beforeCommand);
|
|
editor.commands.on("afterExec", this.afterCommand);
|
|
|
|
editor.on("mouseup", this.onMouseUp);
|
|
|
|
editor.repl = this;
|
|
|
|
// editor.setOption("enableLineWidgets", true);
|
|
editor.renderer.on("beforeRender", this.measureWidgets);
|
|
editor.renderer.on("afterRender", this.updateWidgets);
|
|
editor.renderer.on("afterRender", this._updateCursorVisibility);
|
|
editor.renderer.on("resize", this._replResize);
|
|
};
|
|
this.detach = function(e) {
|
|
console.log("detach", this.session.getLength(), e);
|
|
if (e && e.session == this.session)
|
|
return; // sometimes attach can be called before setSession
|
|
var editor = this.editor;
|
|
if (!editor)
|
|
return;
|
|
|
|
editor.keyBinding.removeKeyboardHandler(replCommands);
|
|
editor.commands.off("exec", this.beforeCommand);
|
|
editor.commands.off("afterExec", this.afterCommand);
|
|
|
|
editor.off("mouseup", this.onMouseUp);
|
|
editor.off("changeSession", this.detach);
|
|
|
|
delete editor.renderer.$gutterLayer.update;
|
|
this.editor = null;
|
|
editor.repl = null;
|
|
|
|
editor.renderer.off("beforeRender", this.measureWidgets);
|
|
editor.renderer.off("afterRender", this.updateWidgets);
|
|
this.session.lineWidgets.forEach(function(w) {
|
|
if (w && w.el && w.el.parentNode) {
|
|
w._inDocument = false;
|
|
w.el.parentNode.removeChild(w.el);
|
|
}
|
|
});
|
|
|
|
editor.renderer.off("afterRender", this._updateCursorVisibility);
|
|
editor.renderer.off("resize", this._replResize);
|
|
};
|
|
this.navigateHistory = function(dir) {
|
|
var cell = this.getCurrentCell();
|
|
if (!cell || cell.type != "input")
|
|
return false;
|
|
var row = this.editor.getCursorPosition().row;
|
|
if (dir == 1 && cell.range.end.row != row)
|
|
return false;
|
|
else if (dir == -1 && cell.range.start.row != row)
|
|
return false;
|
|
var val = cell.getValue();
|
|
val = this.history.navigateList(dir == -1 ? "prev" : "next", val);
|
|
if (typeof val == "string") {
|
|
dir = -dir;
|
|
if (dir == -1 && val.indexOf("\n") == -1)
|
|
dir = 1;
|
|
cell.setValue(val, dir);
|
|
}
|
|
};
|
|
this.getCurrentCell = function(returnAdjacent) {
|
|
var range = this.editor.getSelectionRange();
|
|
var cell = this.getCellAt(range.start);
|
|
|
|
if (returnAdjacent || cell && cell.range.contains(range.end.row, range.end.column)) {
|
|
return cell;
|
|
}
|
|
};
|
|
this.getCellAt = function(pos) {
|
|
if (pos == undefined)
|
|
pos = { row: this.session.getLength(), column: 0 };
|
|
else if (typeof pos == "number")
|
|
pos = { row: pos, column: 0 };
|
|
|
|
var cells = this.session.replCells;
|
|
|
|
for (var i = pos.row; i > 0; i--) {
|
|
if (cells[i])
|
|
break;
|
|
}
|
|
var cell = cells[i];
|
|
if (!cell)
|
|
return;
|
|
cell.row = i;
|
|
for (var i = pos.row + 1, l = this.session.getLength(); i < l; i++) {
|
|
if (cells[i])
|
|
break;
|
|
}
|
|
cell.endRow = Math.min(i - 1, l - 1);
|
|
cell.range = new Range(cell.row, 0, cell.endRow, Number.MAX_VALUE);
|
|
return cell;
|
|
};
|
|
|
|
this.getSiblingCell = function(dir, cell, type) {
|
|
if (dir == -1) {
|
|
var pos = cell.range.clone().start;
|
|
pos.row--;
|
|
} else {
|
|
var pos = cell.range.clone().end;
|
|
pos.row++;
|
|
}
|
|
cell = this.getCellAt(pos);
|
|
if (!cell)
|
|
return;
|
|
if (!type || cell.type == type)
|
|
return cell;
|
|
return this.getSiblingCell(dir, cell, type);
|
|
};
|
|
|
|
this.moveByCells = function(dir, cell, type) {
|
|
if (dir == "first")
|
|
cell = this.getFirstCell(type);
|
|
else if (dir == "last")
|
|
cell = this.getLastCell(type);
|
|
else
|
|
cell = this.getSiblingCell(dir, cell || this.getCurrentCell(), type);
|
|
|
|
if (cell)
|
|
return this.select(cell.range.end);
|
|
};
|
|
|
|
this.getFirstCell = function(type) {
|
|
var cell = this.getCellAt(0);
|
|
if (type && cell.type != type)
|
|
return this.getSiblingCell(1, cell, type);
|
|
return cell;
|
|
};
|
|
this.getLastCell = function(type) {
|
|
var cell = this.getCellAt(null);
|
|
if (type && cell.type != type)
|
|
return this.getSiblingCell(-1, cell, type);
|
|
return cell;
|
|
};
|
|
|
|
this.removeOutputCell = function(cell) {
|
|
cell = cell || this.getCurrentCell();
|
|
if (cell && cell.type == "input")
|
|
cell = cell.output;
|
|
if (cell)
|
|
this.removeCell(cell);
|
|
};
|
|
|
|
this.removeCell = function(cell) {
|
|
var range = cell.getRange().clone();
|
|
range.start.row--;
|
|
range.start.column = this.session.getLine(range.start.row).length;
|
|
this.session.replace(range, "");
|
|
};
|
|
|
|
this.clear = function() {
|
|
this.session.setValue("");
|
|
this.ensureLastInputCell();
|
|
};
|
|
|
|
this.ensureLastInputCell = function() {
|
|
var end = { row: this.session.getLength(), column: 0 };
|
|
var cell = this.getCellAt(end);
|
|
if (!cell || cell.type != "input") {
|
|
this.insertCell(end, { type: "input" }, true);
|
|
}
|
|
};
|
|
|
|
this.select = function(pos) {
|
|
var sel = this.session.selection;
|
|
if (typeof pos.row == "number" && typeof pos.column == "number") {
|
|
if (sel.rangeCount)
|
|
sel.toSingleRange();
|
|
sel.setRange(Range.fromPoints(pos, pos));
|
|
}
|
|
};
|
|
|
|
this.eval = function(force, cell) {
|
|
cell = cell || this.getCurrentCell();
|
|
if (!cell)
|
|
return;
|
|
if (!force && cell.type != "input")
|
|
cell = cell.input || this.getSiblingCell(1, cell) || this.getSiblingCell(-1, cell);
|
|
var str = cell.getValue();
|
|
if (force || this.evaluator.canEvaluate(str)) {
|
|
this.session.getUndoManager().reset();
|
|
|
|
if (!cell.output || !cell.output.session) {
|
|
cell.output = this.insertCell(cell.range.end, { type: "output" });
|
|
var newCell = this.getSiblingCell(1, cell.output);
|
|
}
|
|
this.history.add(str);
|
|
cell.output.waiting = true;
|
|
cell.output.input = cell;
|
|
|
|
var self = this;
|
|
var success = this.evaluator.evaluate(str, cell.output, function(result) {
|
|
cell.output.waiting = false;
|
|
if (result !== undefined)
|
|
cell.output.setValue(result);
|
|
|
|
var renderer = self.editor.renderer;
|
|
renderer.scrollSelectionIntoView(cell.range.end, cell.range.start);
|
|
renderer.scrollCursorIntoView();
|
|
});
|
|
if (success !== false && newCell && newCell.type != "input") {
|
|
newCell = this.insertCell(cell.output.range.end, { type: "input" });
|
|
this.session.selection.setRange(newCell.range);
|
|
}
|
|
if (cell.output.waiting) {
|
|
cell.output.setPlaceholder("...");
|
|
}
|
|
} else
|
|
this.editor.insert("\n");
|
|
|
|
if (this.evaluator.afterEvaluate)
|
|
this.evaluator.afterEvaluate(cell, newCell);
|
|
};
|
|
this.insertCell = function(pos, options, allowSplit) {
|
|
pos = pos || this.session.selection.getCursor();
|
|
if (!options)
|
|
options = { type: "input" };
|
|
|
|
var cell = !allowSplit && this.getCellAt(pos);
|
|
|
|
if (cell) {
|
|
pos = cell.range.end;
|
|
pos = this.session.insert(pos, "\n");
|
|
}
|
|
var newCell = new ReplCell(options, this.session);
|
|
|
|
pos.row = Math.max(0, Math.min(pos.row, this.session.getLength() - 1));
|
|
|
|
var range = Range.fromPoints(pos, pos);
|
|
range.end.column = Number.MAX_VALUE;
|
|
range.start.column = 0;
|
|
newCell.range = range;
|
|
newCell.row = range.end.row;
|
|
|
|
var oldCell = this.session.replCells[pos.row];
|
|
if (oldCell)
|
|
oldCell.destroy();
|
|
|
|
this.session.replCells[pos.row] = newCell;
|
|
this.$updateSession();
|
|
return newCell;
|
|
};
|
|
|
|
this.$updateSession = function() {
|
|
var session = this.session;
|
|
session.$decorations = [];
|
|
var lastType = "";
|
|
session.replCells.forEach(function(c, row) {
|
|
if (!c) {
|
|
if (lastType)
|
|
session.$decorations[row] = lastType;
|
|
return;
|
|
}
|
|
var dec = "";
|
|
if (c.type == "input") {
|
|
dec = "repl_prompt ";
|
|
lastType = "repl_dots ";
|
|
} else if (c.type == "output") {
|
|
dec = "repl_output ";
|
|
lastType = "repl_nonum ";
|
|
}
|
|
|
|
if (c && c.waiting)
|
|
dec += "waiting ";
|
|
session.$decorations[row] = dec;
|
|
});
|
|
session.addGutterDecoration(0, session.$decorations[0]);
|
|
|
|
session.lineWidgets = session.replCells.map(function(c) {
|
|
return c && c.lineWidget;
|
|
});
|
|
};
|
|
|
|
this.updateCellsOnChange = function(delta) {
|
|
var startRow = delta.start.row;
|
|
var len = delta.end.row - startRow;
|
|
|
|
var range = this.$trackedRange;
|
|
var cells = this.session.replCells;
|
|
if (len === 0) {
|
|
|
|
} else if (delta.action == "remove") {
|
|
var removed = cells.splice(startRow + 1, len);
|
|
removed.forEach(function(cell) {
|
|
if (cell) {
|
|
cell.destroy();
|
|
}
|
|
});
|
|
if (range && range.start.row <= startRow && range.end.row >= startRow) {
|
|
this.$trackedRange.end.row = Math.max(this.$trackedRange.end.row - len, this.$trackedRange.start.row);
|
|
}
|
|
if (cells.length <= 1)
|
|
this.ensureLastInputCell();
|
|
} else {
|
|
var args = Array(len);
|
|
args.unshift(startRow + 1, 0);
|
|
cells.splice.apply(cells, args);
|
|
if (range && range.start.row <= startRow && range.end.row >= startRow) {
|
|
this.$trackedRange.end.row += len;
|
|
}
|
|
}
|
|
this.$updateSession();
|
|
this.session._signal("updateCells");
|
|
};
|
|
|
|
this.$tokenizeRow = function(row) {
|
|
var line = this.doc.getLine(row);
|
|
var state = this.states[row - 1];
|
|
|
|
var cell = this.session.replCells[row];
|
|
if (!cell && !state) {
|
|
cell = this.session.repl.getCellAt(row);
|
|
}
|
|
if (cell) {
|
|
state = cell.tokenizerState || cell.type;
|
|
if (!this.tokenizer.regExps[state])
|
|
state = "start";
|
|
}
|
|
|
|
var data = this.tokenizer.getLineTokens(line, state, row);
|
|
|
|
if (this.states[row] + "" !== data.state + "") {
|
|
if (!this.states[row] || !this.states[row].cellData)
|
|
this.states[row] = data.state;
|
|
this.lines[row + 1] = null;
|
|
if (this.currentLine > row + 1)
|
|
this.currentLine = row + 1;
|
|
} else if (this.currentLine == row) {
|
|
this.currentLine = row + 1;
|
|
}
|
|
if (this.session.repl.onTokenizeRow)
|
|
data.tokens = this.session.repl.onTokenizeRow(row, data.tokens) || data.tokens;
|
|
return this.lines[row] = data.tokens;
|
|
};
|
|
|
|
this.addLineWidget = function(w) {
|
|
var renderer = this.editor.renderer;
|
|
if (w.html && !w.el) {
|
|
w.el = dom.createElement("div");
|
|
w.el.innerHTML = w.html;
|
|
}
|
|
if (w.el) {
|
|
dom.addCssClass(w.el, "ace_lineWidgetContainer");
|
|
renderer.container.appendChild(w.el);
|
|
w._inDocument = true;
|
|
}
|
|
|
|
if (!w.coverGutter) {
|
|
w.el.style.zIndex = 3;
|
|
}
|
|
if (!w.pixelHeight) {
|
|
w.pixelHeight = w.el.offsetHeight;
|
|
}
|
|
if (w.rowCount == null)
|
|
w.rowCount = w.pixelHeight / renderer.layerConfig.lineHeight;
|
|
|
|
this.session._emit("changeFold", { data: { start: { row: w.row }}});
|
|
|
|
this.$updateSession();
|
|
this.updateWidgets(null, renderer);
|
|
return w;
|
|
};
|
|
|
|
this.removeLineWidget = function(w) {
|
|
w._inDocument = false;
|
|
if (w.el && w.el.parentNode)
|
|
w.el.parentNode.removeChild(w.el);
|
|
if (w.editor && w.editor.destroy) try {
|
|
w.editor.destroy();
|
|
} catch (e) {}
|
|
this.session._emit("changeFold", { data: { start: { row: w.row }}});
|
|
this.$updateSession();
|
|
};
|
|
|
|
this.onWidgetChanged = function(w) {
|
|
this.session._changedWidgets.push(w);
|
|
this.editor && this.editor.renderer.updateFull();
|
|
};
|
|
|
|
this.measureWidgets = function(e, renderer) {
|
|
var ws = this.session._changedWidgets;
|
|
var config = renderer.layerConfig;
|
|
|
|
if (!ws || !ws.length) return;
|
|
var min = Infinity;
|
|
for (var i = 0; i < ws.length; i++) {
|
|
var w = ws[i].lineWidget;
|
|
if (!w._inDocument) {
|
|
w._inDocument = true;
|
|
renderer.container.appendChild(w.el);
|
|
}
|
|
|
|
w.h = w.el.offsetHeight;
|
|
|
|
if (!w.fixedWidth) {
|
|
w.w = w.el.offsetWidth;
|
|
w.screenWidth = Math.ceil(w.w / config.characterWidth);
|
|
}
|
|
|
|
var rowCount = w.h / config.lineHeight;
|
|
if (w.coverLine)
|
|
rowCount -= this.session.getRowLineCount(w.row);
|
|
|
|
if (w.rowCount != rowCount) {
|
|
w.rowCount = rowCount;
|
|
if (w.row < min)
|
|
min = w.row;
|
|
}
|
|
}
|
|
if (min != Infinity) {
|
|
this.session._emit("changeFold", { data: { start: { row: min }}});
|
|
this.session.lineWidgetWidth = null;
|
|
}
|
|
this.session._changedWidgets = [];
|
|
};
|
|
|
|
this.updateWidgets = function(e, renderer) {
|
|
var config = renderer.layerConfig;
|
|
var cells = this.session.replCells;
|
|
if (!cells)
|
|
return;
|
|
var first = Math.min(this.firstRow, config.firstRow);
|
|
var last = Math.max(this.lastRow, config.lastRow, cells.length);
|
|
|
|
while (first > 0 && !cells[first])
|
|
first--;
|
|
|
|
this.firstRow = config.firstRow;
|
|
this.lastRow = config.lastRow;
|
|
|
|
renderer.$cursorLayer.config = config;
|
|
for (var i = first; i <= last; i++) {
|
|
var c = cells[i];
|
|
var w = c && c.lineWidget;
|
|
if (!w || !w.el) continue;
|
|
|
|
if (!w._inDocument) {
|
|
w._inDocument = true;
|
|
renderer.container.appendChild(w.el);
|
|
}
|
|
var top = renderer.$cursorLayer.getPixelPosition({ row: i, column: 0 }, true).top;
|
|
if (!w.coverLine)
|
|
top += config.lineHeight * this.session.getRowLineCount(w.row);
|
|
w.el.style.top = top - config.offset + "px";
|
|
|
|
var left = w.coverGutter ? 0 : renderer.gutterWidth;
|
|
if (!w.fixedWidth)
|
|
left -= renderer.scrollLeft;
|
|
w.el.style.left = left + "px";
|
|
|
|
if (w.fixedWidth) {
|
|
w.el.style.right = renderer.scrollBar.getWidth() + "px";
|
|
} else {
|
|
w.el.style.right = "";
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
}).call(Repl.prototype);
|
|
|
|
var History = function() {
|
|
this._data = [];
|
|
this._tempData = Object.create(null);
|
|
this.position = 0;
|
|
};
|
|
History.prototype = {
|
|
add: function(text) {
|
|
if (text && this._data[0] !== text) {
|
|
this._data.unshift(text);
|
|
}
|
|
delete this._tempData[this.position];
|
|
this.position = -1;
|
|
this._tempData[-1] = "";
|
|
return this._data;
|
|
},
|
|
navigateList: function(type, value) {
|
|
var lines = this._data;
|
|
if (value && (lines[this.position] != value)) {
|
|
this._tempData[this.position] = value;
|
|
}
|
|
|
|
if (type == "next") {
|
|
if (this.position <= 0) {
|
|
this.position = -1;
|
|
return this._tempData[this.position] || "";
|
|
}
|
|
var next = Math.max(0, this.position - 1);
|
|
}
|
|
else if (type == "prev")
|
|
next = Math.min(lines.length - 1, this.position + 1);
|
|
else if (type == "last")
|
|
next = Math.max(lines.length - 1, 0);
|
|
else if (type == "first")
|
|
next = 0;
|
|
|
|
if (lines[next] && next != this.position) {
|
|
this.position = next;
|
|
return this._tempData[next] || lines[next];
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
function clonePos(pos) {
|
|
return { row: pos.row, column: pos.column };
|
|
}
|
|
function setPos(pos, newPos) {
|
|
pos.row = newPos.row;
|
|
pos.column = newPos.column;
|
|
}
|
|
function clipPos(pos, range) {
|
|
if (comparePoints(pos, range.start) < 0) {
|
|
setPos(pos, range.start);
|
|
} else if (comparePoints(pos, range.end) > 0) {
|
|
setPos(pos, range.end);
|
|
}
|
|
}
|
|
Range.prototype.clip = function(range) {
|
|
clipPos(this.start, range);
|
|
clipPos(this.end, range);
|
|
};
|
|
|
|
|
|
function setClipToRange(anchor, range) {
|
|
anchor.$clip_default = Anchor.prototype.$clipPositionToDocument;
|
|
if (!range) {
|
|
anchor.$clipPositionToDocument = anchor.$clip_default;
|
|
return;
|
|
}
|
|
anchor.$clipPositionToDocument = function(row, column) {
|
|
var pos = this.$clip_default(row, column);
|
|
clipPos(pos, range);
|
|
return pos;
|
|
};
|
|
}
|
|
|
|
|
|
|
|
// dummy example
|
|
var Evaluator = function() {
|
|
};
|
|
|
|
(function() {
|
|
this.canEvaluate = function(str) {
|
|
return !!str.trim();
|
|
};
|
|
|
|
this.evaluate = function(str, cell, cb) {
|
|
cb("evaluator is missing!");
|
|
};
|
|
}).call(Evaluator.prototype);
|
|
|
|
Repl.fromEditor = function(editor, options) {
|
|
// todo different modes for input and output
|
|
var repl = new Repl(editor.session, options);
|
|
// this should happen on session.changeEditor event
|
|
repl.attach(editor);
|
|
|
|
editor.setOption("showPrintMargin", false);
|
|
return repl;
|
|
};
|
|
exports.Repl = Repl;
|
|
|
|
});
|