mirror of
https://github.com/tengge1/ShadowEditor.git
synced 2026-01-25 15:08:11 +00:00
254 lines
7.3 KiB
JavaScript
254 lines
7.3 KiB
JavaScript
(function (global, factory) {
|
||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||
(factory((global.Shadow = {})));
|
||
}(this, (function (exports) { 'use strict';
|
||
|
||
/**
|
||
* 脚本编辑器
|
||
* @author mrdoob / http://mrdoob.com/
|
||
* @author tengge / https://github.com/tengge1
|
||
* @param {*} container 容器
|
||
*/
|
||
function ScriptEditor(container = document.body) {
|
||
this.delay = null; // 代码校验延迟函数
|
||
this.delayTime = 1000; // 代码校验间隔时间(毫秒)
|
||
|
||
this.mode = 'javascript'; // 模式:json, vertexShader, fragmentShader, javascript
|
||
this.source = ''; // 代码
|
||
|
||
this.errorLines = []; // 代码错误行数
|
||
this.widgets = [];
|
||
|
||
// Code Mirror
|
||
var codemirror = CodeMirror(container, {
|
||
value: '',
|
||
lineNumbers: true,
|
||
matchBrackets: true,
|
||
indentWithTabs: true,
|
||
tabSize: 4,
|
||
indentUnit: 4,
|
||
hintOptions: {
|
||
completeSingle: false
|
||
}
|
||
});
|
||
|
||
codemirror.setOption('theme', 'monokai');
|
||
codemirror.on('change', this.onCodeMirrorChange.bind(this));
|
||
|
||
// 防止回退键删除物体
|
||
var wrapper = codemirror.getWrapperElement();
|
||
wrapper.addEventListener('keydown', event => {
|
||
event.stopPropagation();
|
||
});
|
||
|
||
// tern js 自动完成
|
||
var server = new CodeMirror.TernServer({
|
||
caseInsensitive: true,
|
||
plugins: { threejs: null }
|
||
});
|
||
|
||
// 快捷键
|
||
codemirror.setOption('extraKeys', {
|
||
'Ctrl-Space': cm => { server.complete(cm); },
|
||
'Ctrl-I': cm => { server.showType(cm); },
|
||
'Ctrl-O': cm => { server.showDocs(cm); },
|
||
'Alt-.': cm => { server.jumpToDef(cm); },
|
||
'Alt-,': cm => { server.jumpBack(cm); },
|
||
'Ctrl-Q': cm => { server.rename(cm); },
|
||
'Ctrl-.': cm => { server.selectName(cm); }
|
||
});
|
||
|
||
codemirror.on('cursorActivity', cm => {
|
||
if (this.mode !== 'javascript') {
|
||
return;
|
||
}
|
||
server.updateArgHints(cm);
|
||
});
|
||
|
||
codemirror.on('keypress', (cm, kb) => {
|
||
if (this.mode !== 'javascript') {
|
||
return;
|
||
}
|
||
var typed = String.fromCharCode(kb.which || kb.keyCode);
|
||
if (/[\w\.]/.exec(typed)) {
|
||
server.complete(cm);
|
||
}
|
||
});
|
||
|
||
this.codemirror = codemirror;
|
||
}
|
||
/**
|
||
* 设置编辑器脚本代码
|
||
* @param {*} source 源码
|
||
* @param {*} mode 模式 javascript, vertexShader, fragmentShader, json
|
||
* @param {*} cursorPosition 光标位置
|
||
* @param {*} scrollInfo 滚动信息
|
||
*/
|
||
ScriptEditor.prototype.setValue = function (
|
||
source = '',
|
||
mode = 'javascript',
|
||
cursorPosition = { line: 0, ch: 0 },
|
||
scrollInfo = { left: 0, top: 0 }
|
||
) {
|
||
this.source = source;
|
||
this.mode = mode;
|
||
|
||
var codemirror = this.codemirror;
|
||
|
||
var history = codemirror.getHistory();
|
||
|
||
codemirror.setValue(source);
|
||
|
||
if (mode === 'json') {
|
||
codemirror.setOption('mode', {
|
||
name: 'javascript',
|
||
json: true
|
||
});
|
||
} else if (mode === 'vertexShader' || mode === 'fragmentShader') {
|
||
codemirror.setOption('mode', 'glsl');
|
||
} else {
|
||
codemirror.setOption('mode', mode);
|
||
}
|
||
|
||
codemirror.focus();
|
||
|
||
codemirror.setCursor(cursorPosition);
|
||
codemirror.scrollTo(scrollInfo.left, scrollInfo.top);
|
||
|
||
codemirror.setHistory(history);
|
||
};
|
||
|
||
/**
|
||
* 获取编辑器脚本代码
|
||
*/
|
||
ScriptEditor.prototype.getValue = function () {
|
||
return this.codemirror.getValue();
|
||
};
|
||
|
||
/**
|
||
* 清空编辑器
|
||
*/
|
||
ScriptEditor.prototype.clear = function () {
|
||
this.setValue();
|
||
};
|
||
|
||
// ---------------------- 内部函数 -----------------------------------------
|
||
|
||
/**
|
||
* 代码修改事件
|
||
*/
|
||
ScriptEditor.prototype.onCodeMirrorChange = function () {
|
||
var codemirror = this.codemirror;
|
||
|
||
if (codemirror.state.focused === false) {
|
||
return;
|
||
}
|
||
|
||
if (this.delay) {
|
||
clearTimeout(this.delay);
|
||
}
|
||
|
||
this.delay = setTimeout(() => {
|
||
var code = codemirror.getValue();
|
||
this.validate(code);
|
||
}, this.delayTime);
|
||
};
|
||
|
||
/**
|
||
* 校验编辑器中代码正确性
|
||
* @param {*} string
|
||
*/
|
||
ScriptEditor.prototype.validate = function (string) {
|
||
var codemirror = this.codemirror;
|
||
var mode = this.mode;
|
||
|
||
var errorLines = this.errorLines;
|
||
var widgets = this.widgets;
|
||
|
||
var errors = [];
|
||
|
||
return codemirror.operation(() => {
|
||
while (errorLines.length > 0) {
|
||
codemirror.removeLineClass(errorLines.shift(), 'background', 'errorLine');
|
||
}
|
||
|
||
while (widgets.length > 0) {
|
||
codemirror.removeLineWidget(widgets.shift());
|
||
}
|
||
|
||
switch (mode) {
|
||
case 'javascript':
|
||
try {
|
||
var syntax = esprima.parse(string, { tolerant: true });
|
||
errors = syntax.errors;
|
||
} catch (error) {
|
||
errors.push({
|
||
lineNumber: error.lineNumber - 1,
|
||
message: error.message
|
||
});
|
||
}
|
||
|
||
for (var i = 0; i < errors.length; i++) {
|
||
var error = errors[i];
|
||
error.message = error.message.replace(/Line [0-9]+: /, '');
|
||
}
|
||
break;
|
||
case 'json':
|
||
jsonlint.parseError = (message, info) => {
|
||
message = message.split('\n')[3];
|
||
errors.push({
|
||
lineNumber: info.loc.first_line - 1,
|
||
message: message
|
||
});
|
||
};
|
||
|
||
try {
|
||
jsonlint.parse(string);
|
||
} catch (error) {
|
||
// ignore failed error recovery
|
||
}
|
||
break;
|
||
case 'vertexShader':
|
||
case 'fragmentShader':
|
||
try {
|
||
var shaderType = mode === 'vertexShader' ? glslprep.Shader.VERTEX : glslprep.Shader.FRAGMENT;
|
||
glslprep.parseGlsl(string, shaderType);
|
||
} catch (error) {
|
||
if (error instanceof glslprep.SyntaxError) {
|
||
errors.push({
|
||
lineNumber: error.line,
|
||
message: "Syntax Error: " + error.message
|
||
});
|
||
} else {
|
||
console.error(error.stack || error);
|
||
}
|
||
}
|
||
}
|
||
|
||
for (var i = 0; i < errors.length; i++) {
|
||
var error = errors[i];
|
||
|
||
var message = document.createElement('div');
|
||
message.className = 'esprima-error';
|
||
message.textContent = error.message;
|
||
|
||
var lineNumber = Math.max(error.lineNumber, 0);
|
||
errorLines.push(lineNumber);
|
||
|
||
codemirror.addLineClass(lineNumber, 'background', 'errorLine');
|
||
|
||
var widget = codemirror.addLineWidget(lineNumber, message);
|
||
widgets.push(widget);
|
||
}
|
||
|
||
return errors.length === 0;
|
||
});
|
||
};
|
||
|
||
exports.ScriptEditor = ScriptEditor;
|
||
|
||
Object.defineProperty(exports, '__esModule', { value: true });
|
||
|
||
})));
|