superpowers-game-ftext-plugin
Version:
Generic text asset for the Superpowers Game system of Superpowers, the extensible HTML5 2D+3D game engine.
355 lines (354 loc) • 17.2 kB
JavaScript
var OT = require("operational-transform");
window.CodeMirror = require("codemirror");
require("codemirror/addon/search/search");
require("codemirror/addon/search/searchcursor");
require("codemirror/addon/edit/closebrackets");
require("codemirror/addon/comment/comment");
require("codemirror/addon/hint/show-hint");
require("codemirror/addon/selection/active-line");
require("codemirror/keymap/sublime");
require("codemirror/mode/javascript/javascript");
require("codemirror/mode/clike/clike");
// added for fText plugin
require("codemirror/addon/fold/foldcode");
require("codemirror/addon/fold/foldgutter");
require("codemirror/addon/fold/brace-fold");
require("codemirror/addon/fold/comment-fold");
require("codemirror/addon/fold/indent-fold");
require("codemirror/addon/fold/xml-fold");
require("codemirror/addon/fold/markdown-fold");
require("codemirror/addon/search/match-highlighter");
require("codemirror/addon/edit/matchtags"); // depends on xml-fold
require("codemirror/addon/edit/trailingspace");
require("codemirror/addon/edit/closetag"); // depends on xml-fold
require("codemirror/addon/selection/active-line");
require("codemirror/addon/hint/anyword-hint");
require("../codemirror-linters/lint");
window.CSSLint = require("csslint").CSSLint;
require("codemirror/addon/lint/css-lint");
window.JSHINT = require("jshint").JSHINT;
require("codemirror/addon/lint/javascript-lint");
window.jsonlint = require("jsonlint");
require("../codemirror-linters/json-lint");
window.csonparser = require("cson-parser");
require("../codemirror-linters/cson-lint");
window.jade = require("jade");
require("../codemirror-linters/jade-lint");
window.stylus = require("stylus");
require("../codemirror-linters/stylus-lint");
window.jsyaml = require("js-yaml");
require("../codemirror-linters/yaml-lint");
require("codemirror/keymap/emacs");
require("codemirror/keymap/vim");
require("codemirror/mode/htmlmixed/htmlmixed"); // load js, css, xml
require("codemirror/mode/jade/jade");
require("codemirror/mode/markdown/markdown"); // load xml
require("codemirror/mode/coffeescript/coffeescript");
require("codemirror/mode/stylus/stylus");
require("codemirror/mode/yaml/yaml");
// /added for fText plugin
var fTextEditorWidget = (function () {
function fTextEditorWidget(projectClient, clientId, textArea, options) {
var _this = this;
this.tmpCodeMirrorDoc = new CodeMirror.Doc("");
this.texts = [];
this.undoStack = [];
this.undoQuantityByAction = [0];
this.redoStack = [];
this.redoQuantityByAction = [0];
this.useSoftTab = true; // use spaces as tabs = true (indent with tabs = false)
this.beforeChange = function (instance, change) {
if (change.origin === "setValue" || change.origin === "network")
return;
var lastText = instance.getDoc().getValue();
if (lastText !== _this.texts[_this.texts.length - 1])
_this.texts.push(lastText);
};
this.edit = function (instance, changes) {
if (_this.editCallback != null)
_this.editCallback(_this.codeMirrorInstance.getDoc().getValue(), changes[0].origin);
var undoRedo = false;
var operationToSend;
for (var changeIndex = 0; changeIndex < changes.length; changeIndex++) {
var change = changes[changeIndex];
var origin = change.origin;
// Modification from an other person
if (origin === "setValue" || origin === "network")
continue;
_this.tmpCodeMirrorDoc.setValue(_this.texts[changeIndex]);
var operation = new OT.TextOperation(_this.clientId);
for (var line = 0; line < change.from.line; line++)
operation.retain(_this.tmpCodeMirrorDoc.getLine(line).length + 1);
operation.retain(change.from.ch);
var offset = 0;
if (change.removed.length !== 1 || change.removed[0] !== "") {
for (var index = 0; index < change.removed.length; index++) {
var text = change.removed[index];
if (index !== 0) {
operation.delete("\n");
offset += 1;
}
operation.delete(text);
offset += text.length;
}
}
if (change.text.length !== 1 || change.text[0] !== "") {
for (var index = 0; index < change.text.length; index++) {
if (index !== 0)
operation.insert("\n");
operation.insert(change.text[index]);
}
}
var beforeLength = (operation.ops[0].attributes.amount != null) ? operation.ops[0].attributes.amount : 0;
operation.retain(_this.tmpCodeMirrorDoc.getValue().length - beforeLength - offset);
if (operationToSend == null)
operationToSend = operation.clone();
else
operationToSend = operationToSend.compose(operation);
if (origin === "undo" || origin === "redo")
undoRedo = true;
}
_this.texts.length = 0;
if (operationToSend == null)
return;
if (!undoRedo) {
if (_this.undoTimeout != null) {
clearTimeout(_this.undoTimeout);
_this.undoTimeout = null;
}
_this.undoStack.push(operationToSend.clone().invert());
_this.undoQuantityByAction[_this.undoQuantityByAction.length - 1] += 1;
if (_this.undoQuantityByAction[_this.undoQuantityByAction.length - 1] > 20)
_this.undoQuantityByAction.push(0);
else {
_this.undoTimeout = window.setTimeout(function () {
_this.undoTimeout = null;
_this.undoQuantityByAction.push(0);
}, 500);
}
_this.redoStack.length = 0;
_this.redoQuantityByAction.length = 0;
}
if (_this.sentOperation == null) {
_this.sendOperationCallback(operationToSend.serialize());
_this.sentOperation = operationToSend;
}
else {
if (_this.pendingOperation != null)
_this.pendingOperation = _this.pendingOperation.compose(operationToSend);
else
_this.pendingOperation = operationToSend;
}
};
this.onResourceReceived = function (resourceId, resource) {
_this.textEditorResource = resource;
_this.codeMirrorInstance.setOption("tabSize", resource.pub.tabSize);
_this.codeMirrorInstance.setOption("indentUnit", resource.pub.tabSize);
_this.codeMirrorInstance.setOption("indentWithTabs", resource.pub.indentWithTabs);
_this.useSoftTab = !resource.pub.indentWithTabs;
};
this.onResourceEdited = function (resourceId, command, propertyName) {
switch (propertyName) {
case "tabSize":
_this.codeMirrorInstance.setOption("tabSize", _this.textEditorResource.pub.tabSize);
_this.codeMirrorInstance.setOption("indentUnit", _this.textEditorResource.pub.tabSize);
break;
case "indentWithTabs":
_this.useSoftTab = !_this.textEditorResource.pub.indentWithTabs;
_this.codeMirrorInstance.setOption("indentWithTabs", _this.textEditorResource.pub.indentWithTabs);
break;
}
};
var extraKeys = {
"F9": function () { },
"Tab": function (cm) {
if (cm.getSelection() !== "")
cm.execCommand("indentMore");
else {
if (_this.useSoftTab)
cm.execCommand("insertSoftTab");
else
cm.execCommand("insertTab");
}
},
"Cmd-X": function () { document.execCommand("cut"); },
"Cmd-C": function () { document.execCommand("copy"); },
"Cmd-V": function () { document.execCommand("paste"); },
"Ctrl-Z": function () { _this.undo(); },
"Cmd-Z": function () { _this.undo(); },
"Shift-Ctrl-Z": function () { _this.redo(); },
"Shift-Cmd-Z": function () { _this.redo(); },
"Ctrl-Y": function () { _this.redo(); },
"Cmd-Y": function () { _this.redo(); },
"Ctrl-S": function () { _this.saveCallback(); },
"Cmd-S": function () { _this.saveCallback(); }
};
if (options.extraKeys != null) {
for (var keyName in options.extraKeys) {
extraKeys[keyName] = options.extraKeys[keyName];
}
}
this.editCallback = options.editCallback;
this.sendOperationCallback = options.sendOperationCallback;
this.saveCallback = options.saveCallback;
var editorConfig = {
// theme: "monokai",
lineNumbers: true, matchBrackets: true, styleActiveLine: true, autoCloseBrackets: true,
gutters: ["CodeMirror-linenumbers"],
indentWithTabs: false, indentUnit: 2, tabSize: 2,
extraKeys: extraKeys, keyMap: "sublime",
viewportMargin: Infinity,
mode: options.mode,
readOnly: true
};
this.codeMirrorInstance = CodeMirror.fromTextArea(textArea, editorConfig);
this.codeMirrorInstance.on("changes", this.edit);
this.codeMirrorInstance.on("beforeChange", this.beforeChange);
this.clientId = clientId;
projectClient.subResource("fTextSettings", this);
}
fTextEditorWidget.prototype.setText = function (text) {
this.codeMirrorInstance.getDoc().setValue(text);
this.codeMirrorInstance.getDoc().clearHistory();
this.codeMirrorInstance.setOption("readOnly", false);
this.codeMirrorInstance.focus();
};
fTextEditorWidget.prototype.receiveEditText = function (operationData) {
if (this.clientId === operationData.userId) {
if (this.pendingOperation != null) {
this.sendOperationCallback(this.pendingOperation.serialize());
this.sentOperation = this.pendingOperation;
this.pendingOperation = null;
}
else
this.sentOperation = null;
return;
}
// Transform operation and local changes
var operation = new OT.TextOperation();
operation.deserialize(operationData);
if (this.sentOperation != null) {
_a = this.sentOperation.transform(operation), this.sentOperation = _a[0], operation = _a[1];
if (this.pendingOperation != null)
_b = this.pendingOperation.transform(operation), this.pendingOperation = _b[0], operation = _b[1];
}
this.undoStack = transformStack(this.undoStack, operation);
this.redoStack = transformStack(this.redoStack, operation);
this.applyOperation(operation.clone(), "network", false);
var _a, _b;
};
fTextEditorWidget.prototype.applyOperation = function (operation, origin, moveCursor) {
var cursorPosition = 0;
var line = 0;
for (var _i = 0, _a = operation.ops; _i < _a.length; _i++) {
var op = _a[_i];
switch (op.type) {
case "retain": {
while (true) {
if (op.attributes.amount <= this.codeMirrorInstance.getDoc().getLine(line).length - cursorPosition)
break;
op.attributes.amount -= this.codeMirrorInstance.getDoc().getLine(line).length + 1 - cursorPosition;
cursorPosition = 0;
line++;
}
cursorPosition += op.attributes.amount;
break;
}
case "insert": {
var cursor = this.codeMirrorInstance.getDoc().getCursor();
var texts = op.attributes.text.split("\n");
for (var textIndex = 0; textIndex < texts.length; textIndex++) {
var text = texts[textIndex];
if (textIndex !== texts.length - 1)
text += "\n";
this.codeMirrorInstance.replaceRange(text, { line: line, ch: cursorPosition }, null, origin);
cursorPosition += text.length;
if (textIndex !== texts.length - 1) {
cursorPosition = 0;
line++;
}
}
if (line === cursor.line && cursorPosition === cursor.ch) {
if (!operation.gotPriority(this.clientId)) {
for (var i = 0; i < op.attributes.text.length; i++)
this.codeMirrorInstance.execCommand("goCharLeft");
}
}
if (moveCursor)
this.codeMirrorInstance.setCursor(line, cursorPosition);
//use this way insted ? this.codeMirrorInstance.getDoc().setCursor({ line, ch: cursorPosition });
break;
}
case "delete": {
var texts = op.attributes.text.split("\n");
for (var textIndex = 0; textIndex < texts.length; textIndex++) {
var text = texts[textIndex];
if (texts[textIndex + 1] != null)
this.codeMirrorInstance.replaceRange("", { line: line, ch: cursorPosition }, { line: line + 1, ch: 0 }, origin);
else
this.codeMirrorInstance.replaceRange("", { line: line, ch: cursorPosition }, { line: line, ch: cursorPosition + text.length }, origin);
if (moveCursor)
this.codeMirrorInstance.setCursor(line, cursorPosition);
}
break;
}
}
}
};
fTextEditorWidget.prototype.undo = function () {
if (this.undoStack.length === 0)
return;
if (this.undoQuantityByAction[this.undoQuantityByAction.length - 1] === 0)
this.undoQuantityByAction.pop();
var undoQuantityByAction = this.undoQuantityByAction[this.undoQuantityByAction.length - 1];
for (var i = 0; i < undoQuantityByAction; i++) {
var operationToUndo = this.undoStack[this.undoStack.length - 1];
this.applyOperation(operationToUndo.clone(), "undo", true);
this.undoStack.pop();
this.redoStack.push(operationToUndo.invert());
}
if (this.undoTimeout != null) {
clearTimeout(this.undoTimeout);
this.undoTimeout = null;
}
this.redoQuantityByAction.push(this.undoQuantityByAction[this.undoQuantityByAction.length - 1]);
this.undoQuantityByAction[this.undoQuantityByAction.length - 1] = 0;
};
fTextEditorWidget.prototype.redo = function () {
if (this.redoStack.length === 0)
return;
var redoQuantityByAction = this.redoQuantityByAction[this.redoQuantityByAction.length - 1];
for (var i = 0; i < redoQuantityByAction; i++) {
var operationToRedo = this.redoStack[this.redoStack.length - 1];
this.applyOperation(operationToRedo.clone(), "undo", true);
this.redoStack.pop();
this.undoStack.push(operationToRedo.invert());
}
if (this.undoTimeout != null) {
clearTimeout(this.undoTimeout);
this.undoTimeout = null;
this.undoQuantityByAction.push(this.redoQuantityByAction[this.redoQuantityByAction.length - 1]);
}
else
this.undoQuantityByAction[this.undoQuantityByAction.length - 1] = this.redoQuantityByAction[this.redoQuantityByAction.length - 1];
this.undoQuantityByAction.push(0);
this.redoQuantityByAction.pop();
};
fTextEditorWidget.prototype.clear = function () {
if (this.undoTimeout != null)
clearTimeout(this.undoTimeout);
};
return fTextEditorWidget;
})();
function transformStack(stack, operation) {
if (stack.length === 0)
return stack;
var newStack = [];
for (var i = stack.length - 1; i > 0; i--) {
var pair = stack[i].transform(operation);
newStack.push(pair[0]);
operation = pair[1];
}
return newStack.reverse();
}
module.exports = fTextEditorWidget;