diff --git a/config.json b/config.json index d58f0b2d..4a0dccf7 100644 --- a/config.json +++ b/config.json @@ -1,7 +1,7 @@ { "cache" : {"allowed" : false}, "minification" : { - "js" : false, + "js" : true, "css" : true, "html" : true, "img" : true diff --git a/lib/client/editor.js b/lib/client/editor.js index e7da9f7d..6711cdd1 100644 --- a/lib/client/editor.js +++ b/lib/client/editor.js @@ -44,13 +44,15 @@ CloudCommander.Editor.CodeMirror = { '}' }); - CloudCommander.jsload('lib/client/editor/codemirror/pack/xml.pack.js', + CloudCommander.jsload('lib/client/editor/' + + 'codemirror/pack/javascript.pack.js', createEditorDiv); }; }; /* load CodeMirror main module */ - CloudCommander.jsload('lib/client/editor/codemirror/pack/codemirror.pack.js', + CloudCommander.jsload('lib/client/editor/' + + 'codemirror/pack/codemirror.pack.js', loadAll(this)); }), diff --git a/lib/client/editor/codemirror/mode/javascript.js b/lib/client/editor/codemirror/mode/javascript.js new file mode 100644 index 00000000..2c2819af --- /dev/null +++ b/lib/client/editor/codemirror/mode/javascript.js @@ -0,0 +1,361 @@ +CodeMirror.defineMode("javascript", function(config, parserConfig) { + var indentUnit = config.indentUnit; + var jsonMode = parserConfig.json; + + // Tokenizer + + var keywords = function(){ + function kw(type) {return {type: type, style: "keyword"};} + var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); + var operator = kw("operator"), atom = {type: "atom", style: "atom"}; + return { + "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, + "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, + "var": kw("var"), "const": kw("var"), "let": kw("var"), + "function": kw("function"), "catch": kw("catch"), + "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), + "in": operator, "typeof": operator, "instanceof": operator, + "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom + }; + }(); + + var isOperatorChar = /[+\-*&%=<>!?|]/; + + function chain(stream, state, f) { + state.tokenize = f; + return f(stream, state); + } + + function nextUntilUnescaped(stream, end) { + var escaped = false, next; + while ((next = stream.next()) != null) { + if (next == end && !escaped) + return false; + escaped = !escaped && next == "\\"; + } + return escaped; + } + + // Used as scratch variables to communicate multiple values without + // consing up tons of objects. + var type, content; + function ret(tp, style, cont) { + type = tp; content = cont; + return style; + } + + function jsTokenBase(stream, state) { + var ch = stream.next(); + if (ch == '"' || ch == "'") + return chain(stream, state, jsTokenString(ch)); + else if (/[\[\]{}\(\),;\:\.]/.test(ch)) + return ret(ch); + else if (ch == "0" && stream.eat(/x/i)) { + stream.eatWhile(/[\da-f]/i); + return ret("number", "number"); + } + else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) { + stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); + return ret("number", "number"); + } + else if (ch == "/") { + if (stream.eat("*")) { + return chain(stream, state, jsTokenComment); + } + else if (stream.eat("/")) { + stream.skipToEnd(); + return ret("comment", "comment"); + } + else if (state.reAllowed) { + nextUntilUnescaped(stream, "/"); + stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla + return ret("regexp", "string-2"); + } + else { + stream.eatWhile(isOperatorChar); + return ret("operator", null, stream.current()); + } + } + else if (ch == "#") { + stream.skipToEnd(); + return ret("error", "error"); + } + else if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return ret("operator", null, stream.current()); + } + else { + stream.eatWhile(/[\w\$_]/); + var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; + return (known && state.kwAllowed) ? ret(known.type, known.style, word) : + ret("variable", "variable", word); + } + } + + function jsTokenString(quote) { + return function(stream, state) { + if (!nextUntilUnescaped(stream, quote)) + state.tokenize = jsTokenBase; + return ret("string", "string"); + }; + } + + function jsTokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = jsTokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return ret("comment", "comment"); + } + + // Parser + + var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; + + function JSLexical(indented, column, type, align, prev, info) { + this.indented = indented; + this.column = column; + this.type = type; + this.prev = prev; + this.info = info; + if (align != null) this.align = align; + } + + function inScope(state, varname) { + for (var v = state.localVars; v; v = v.next) + if (v.name == varname) return true; + } + + function parseJS(state, style, type, content, stream) { + var cc = state.cc; + // Communicate our context to the combinators. + // (Less wasteful than consing up a hundred closures on every call.) + cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; + + if (!state.lexical.hasOwnProperty("align")) + state.lexical.align = true; + + while(true) { + var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; + if (combinator(type, content)) { + while(cc.length && cc[cc.length - 1].lex) + cc.pop()(); + if (cx.marked) return cx.marked; + if (type == "variable" && inScope(state, content)) return "variable-2"; + return style; + } + } + } + + // Combinator utils + + var cx = {state: null, column: null, marked: null, cc: null}; + function pass() { + for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); + } + function cont() { + pass.apply(null, arguments); + return true; + } + function register(varname) { + var state = cx.state; + if (state.context) { + cx.marked = "def"; + for (var v = state.localVars; v; v = v.next) + if (v.name == varname) return; + state.localVars = {name: varname, next: state.localVars}; + } + } + + // Combinators + + var defaultVars = {name: "this", next: {name: "arguments"}}; + function pushcontext() { + if (!cx.state.context) cx.state.localVars = defaultVars; + cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; + } + function popcontext() { + cx.state.localVars = cx.state.context.vars; + cx.state.context = cx.state.context.prev; + } + function pushlex(type, info) { + var result = function() { + var state = cx.state; + state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info); + }; + result.lex = true; + return result; + } + function poplex() { + var state = cx.state; + if (state.lexical.prev) { + if (state.lexical.type == ")") + state.indented = state.lexical.indented; + state.lexical = state.lexical.prev; + } + } + poplex.lex = true; + + function expect(wanted) { + return function expecting(type) { + if (type == wanted) return cont(); + else if (wanted == ";") return pass(); + else return cont(arguments.callee); + }; + } + + function statement(type) { + if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex); + if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); + if (type == "keyword b") return cont(pushlex("form"), statement, poplex); + if (type == "{") return cont(pushlex("}"), block, poplex); + if (type == ";") return cont(); + if (type == "function") return cont(functiondef); + if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), + poplex, statement, poplex); + if (type == "variable") return cont(pushlex("stat"), maybelabel); + if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), + block, poplex, poplex); + if (type == "case") return cont(expression, expect(":")); + if (type == "default") return cont(expect(":")); + if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), + statement, poplex, popcontext); + return pass(pushlex("stat"), expression, expect(";"), poplex); + } + function expression(type) { + if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator); + if (type == "function") return cont(functiondef); + if (type == "keyword c") return cont(maybeexpression); + if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator); + if (type == "operator") return cont(expression); + if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator); + if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator); + return cont(); + } + function maybeexpression(type) { + if (type.match(/[;\}\)\],]/)) return pass(); + return pass(expression); + } + + function maybeoperator(type, value) { + if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator); + if (type == "operator" || type == ":") return cont(expression); + if (type == ";") return; + if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator); + if (type == ".") return cont(property, maybeoperator); + if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); + } + function maybelabel(type) { + if (type == ":") return cont(poplex, statement); + return pass(maybeoperator, expect(";"), poplex); + } + function property(type) { + if (type == "variable") {cx.marked = "property"; return cont();} + } + function objprop(type) { + if (type == "variable") cx.marked = "property"; + if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression); + } + function commasep(what, end) { + function proceed(type) { + if (type == ",") return cont(what, proceed); + if (type == end) return cont(); + return cont(expect(end)); + } + return function commaSeparated(type) { + if (type == end) return cont(); + else return pass(what, proceed); + }; + } + function block(type) { + if (type == "}") return cont(); + return pass(statement, block); + } + function vardef1(type, value) { + if (type == "variable"){register(value); return cont(vardef2);} + return cont(); + } + function vardef2(type, value) { + if (value == "=") return cont(expression, vardef2); + if (type == ",") return cont(vardef1); + } + function forspec1(type) { + if (type == "var") return cont(vardef1, forspec2); + if (type == ";") return pass(forspec2); + if (type == "variable") return cont(formaybein); + return pass(forspec2); + } + function formaybein(type, value) { + if (value == "in") return cont(expression); + return cont(maybeoperator, forspec2); + } + function forspec2(type, value) { + if (type == ";") return cont(forspec3); + if (value == "in") return cont(expression); + return cont(expression, expect(";"), forspec3); + } + function forspec3(type) { + if (type != ")") cont(expression); + } + function functiondef(type, value) { + if (type == "variable") {register(value); return cont(functiondef);} + if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext); + } + function funarg(type, value) { + if (type == "variable") {register(value); return cont();} + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: jsTokenBase, + reAllowed: true, + kwAllowed: true, + cc: [], + lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), + localVars: parserConfig.localVars, + context: parserConfig.localVars && {vars: parserConfig.localVars}, + indented: 0 + }; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (!state.lexical.hasOwnProperty("align")) + state.lexical.align = false; + state.indented = stream.indentation(); + } + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + if (type == "comment") return style; + state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/)); + state.kwAllowed = type != '.'; + return parseJS(state, style, type, content, stream); + }, + + indent: function(state, textAfter) { + if (state.tokenize != jsTokenBase) return 0; + var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; + if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; + var type = lexical.type, closing = firstChar == type; + if (type == "vardef") return lexical.indented + 4; + else if (type == "form" && firstChar == "{") return lexical.indented; + else if (type == "stat" || type == "form") return lexical.indented + indentUnit; + else if (lexical.info == "switch" && !closing) + return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); + else if (lexical.align) return lexical.column + (closing ? 0 : 1); + else return lexical.indented + (closing ? 0 : indentUnit); + }, + + electricChars: ":{}" + }; +}); + +CodeMirror.defineMIME("text/javascript", "javascript"); +CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); \ No newline at end of file diff --git a/lib/client/editor/codemirror/pack/javascript.pack.js b/lib/client/editor/codemirror/pack/javascript.pack.js new file mode 100644 index 00000000..e413ff7a --- /dev/null +++ b/lib/client/editor/codemirror/pack/javascript.pack.js @@ -0,0 +1 @@ +CodeMirror.defineMode("javascript",function(config,parserConfig){function chain(stream,state,f){return state.tokenize=f,f(stream,state)}function nextUntilUnescaped(stream,end){var escaped=!1,next;while((next=stream.next())!=null){if(next==end&&!escaped)return!1;escaped=!escaped&&next=="\\"}return escaped}function ret(tp,style,cont){return type=tp,content=cont,style}function jsTokenBase(stream,state){var ch=stream.next();if(ch=='"'||ch=="'")return chain(stream,state,jsTokenString(ch));if(/[\[\]{}\(\),;\:\.]/.test(ch))return ret(ch);if(ch=="0"&&stream.eat(/x/i))return stream.eatWhile(/[\da-f]/i),ret("number","number");if(/\d/.test(ch)||ch=="-"&&stream.eat(/\d/))return stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/),ret("number","number");if(ch=="/")return stream.eat("*")?chain(stream,state,jsTokenComment):stream.eat("/")?(stream.skipToEnd(),ret("comment","comment")):state.reAllowed?(nextUntilUnescaped(stream,"/"),stream.eatWhile(/[gimy]/),ret("regexp","string-2")):(stream.eatWhile(isOperatorChar),ret("operator",null,stream.current()));if(ch=="#")return stream.skipToEnd(),ret("error","error");if(isOperatorChar.test(ch))return stream.eatWhile(isOperatorChar),ret("operator",null,stream.current());stream.eatWhile(/[\w\$_]/);var word=stream.current(),known=keywords.propertyIsEnumerable(word)&&keywords[word];return known&&state.kwAllowed?ret(known.type,known.style,word):ret("variable","variable",word)}function jsTokenString(quote){return function(stream,state){return nextUntilUnescaped(stream,quote)||(state.tokenize=jsTokenBase),ret("string","string")}}function jsTokenComment(stream,state){var maybeEnd=!1,ch;while(ch=stream.next()){if(ch=="/"&&maybeEnd){state.tokenize=jsTokenBase;break}maybeEnd=ch=="*"}return ret("comment","comment")}function JSLexical(indented,column,type,align,prev,info){this.indented=indented,this.column=column,this.type=type,this.prev=prev,this.info=info,align!=null&&(this.align=align)}function inScope(state,varname){for(var v=state.localVars;v;v=v.next)if(v.name==varname)return!0}function parseJS(state,style,type,content,stream){var cc=state.cc;cx.state=state,cx.stream=stream,cx.marked=null,cx.cc=cc,state.lexical.hasOwnProperty("align")||(state.lexical.align=!0);for(;;){var combinator=cc.length?cc.pop():jsonMode?expression:statement;if(combinator(type,content)){while(cc.length&&cc[cc.length-1].lex)cc.pop()();return cx.marked?cx.marked:type=="variable"&&inScope(state,content)?"variable-2":style}}}function pass(){for(var i=arguments.length-1;i>=0;i--)cx.cc.push(arguments[i])}function cont(){return pass.apply(null,arguments),!0}function register(varname){var state=cx.state;if(state.context){cx.marked="def";for(var v=state.localVars;v;v=v.next)if(v.name==varname)return;state.localVars={name:varname,next:state.localVars}}}function pushcontext(){cx.state.context||(cx.state.localVars=defaultVars),cx.state.context={prev:cx.state.context,vars:cx.state.localVars}}function popcontext(){cx.state.localVars=cx.state.context.vars,cx.state.context=cx.state.context.prev}function pushlex(type,info){var result=function(){var state=cx.state;state.lexical=new JSLexical(state.indented,cx.stream.column(),type,null,state.lexical,info)};return result.lex=!0,result}function poplex(){var state=cx.state;state.lexical.prev&&(state.lexical.type==")"&&(state.indented=state.lexical.indented),state.lexical=state.lexical.prev)}function expect(wanted){return function(type){return type==wanted?cont():wanted==";"?pass():cont(arguments.callee)}}function statement(type){return type=="var"?cont(pushlex("vardef"),vardef1,expect(";"),poplex):type=="keyword a"?cont(pushlex("form"),expression,statement,poplex):type=="keyword b"?cont(pushlex("form"),statement,poplex):type=="{"?cont(pushlex("}"),block,poplex):type==";"?cont():type=="function"?cont(functiondef):type=="for"?cont(pushlex("form"),expect("("),pushlex(")"),forspec1,expect(")"),poplex,statement,poplex):type=="variable"?cont(pushlex("stat"),maybelabel):type=="switch"?cont(pushlex("form"),expression,pushlex("}","switch"),expect("{"),block,poplex,poplex):type=="case"?cont(expression,expect(":")):type=="default"?cont(expect(":")):type=="catch"?cont(pushlex("form"),pushcontext,expect("("),funarg,expect(")"),statement,poplex,popcontext):pass(pushlex("stat"),expression,expect(";"),poplex)}function expression(type){return atomicTypes.hasOwnProperty(type)?cont(maybeoperator):type=="function"?cont(functiondef):type=="keyword c"?cont(maybeexpression):type=="("?cont(pushlex(")"),maybeexpression,expect(")"),poplex,maybeoperator):type=="operator"?cont(expression):type=="["?cont(pushlex("]"),commasep(expression,"]"),poplex,maybeoperator):type=="{"?cont(pushlex("}"),commasep(objprop,"}"),poplex,maybeoperator):cont()}function maybeexpression(type){return type.match(/[;\}\)\],]/)?pass():pass(expression)}function maybeoperator(type,value){if(type=="operator"&&/\+\+|--/.test(value))return cont(maybeoperator);if(type=="operator"||type==":")return cont(expression);if(type==";")return;if(type=="(")return cont(pushlex(")"),commasep(expression,")"),poplex,maybeoperator);if(type==".")return cont(property,maybeoperator);if(type=="[")return cont(pushlex("]"),expression,expect("]"),poplex,maybeoperator)}function maybelabel(type){return type==":"?cont(poplex,statement):pass(maybeoperator,expect(";"),poplex)}function property(type){if(type=="variable")return cx.marked="property",cont()}function objprop(type){type=="variable"&&(cx.marked="property");if(atomicTypes.hasOwnProperty(type))return cont(expect(":"),expression)}function commasep(what,end){function proceed(type){return type==","?cont(what,proceed):type==end?cont():cont(expect(end))}return function(type){return type==end?cont():pass(what,proceed)}}function block(type){return type=="}"?cont():pass(statement,block)}function vardef1(type,value){return type=="variable"?(register(value),cont(vardef2)):cont()}function vardef2(type,value){if(value=="=")return cont(expression,vardef2);if(type==",")return cont(vardef1)}function forspec1(type){return type=="var"?cont(vardef1,forspec2):type==";"?pass(forspec2):type=="variable"?cont(formaybein):pass(forspec2)}function formaybein(type,value){return value=="in"?cont(expression):cont(maybeoperator,forspec2)}function forspec2(type,value){return type==";"?cont(forspec3):value=="in"?cont(expression):cont(expression,expect(";"),forspec3)}function forspec3(type){type!=")"&&cont(expression)}function functiondef(type,value){if(type=="variable")return register(value),cont(functiondef);if(type=="(")return cont(pushlex(")"),pushcontext,commasep(funarg,")"),poplex,statement,popcontext)}function funarg(type,value){if(type=="variable")return register(value),cont()}var indentUnit=config.indentUnit,jsonMode=parserConfig.json,keywords=function(){function kw(type){return{type:type,style:"keyword"}}var A=kw("keyword a"),B=kw("keyword b"),C=kw("keyword c"),operator=kw("operator"),atom={type:"atom",style:"atom"};return{"if":A,"while":A,"with":A,"else":B,"do":B,"try":B,"finally":B,"return":C,"break":C,"continue":C,"new":C,"delete":C,"throw":C,"var":kw("var"),"const":kw("var"),let:kw("var"),"function":kw("function"),"catch":kw("catch"),"for":kw("for"),"switch":kw("switch"),"case":kw("case"),"default":kw("default"),"in":operator,"typeof":operator,"instanceof":operator,"true":atom,"false":atom,"null":atom,"undefined":atom,NaN:atom,Infinity:atom}}(),isOperatorChar=/[+\-*&%=<>!?|]/,type,content,atomicTypes={atom:!0,number:!0,variable:!0,string:!0,regexp:!0},cx={state:null,column:null,marked:null,cc:null},defaultVars={name:"this",next:{name:"arguments"}};return poplex.lex=!0,{startState:function(basecolumn){return{tokenize:jsTokenBase,reAllowed:!0,kwAllowed:!0,cc:[],lexical:new JSLexical((basecolumn||0)-indentUnit,0,"block",!1),localVars:parserConfig.localVars,context:parserConfig.localVars&&{vars:parserConfig.localVars},indented:0}},token:function(stream,state){stream.sol()&&(state.lexical.hasOwnProperty("align")||(state.lexical.align=!1),state.indented=stream.indentation());if(stream.eatSpace())return null;var style=state.tokenize(stream,state);return type=="comment"?style:(state.reAllowed=type=="operator"||type=="keyword c"||!!type.match(/^[\[{}\(,;:]$/),state.kwAllowed=type!=".",parseJS(state,style,type,content,stream))},indent:function(state,textAfter){if(state.tokenize!=jsTokenBase)return 0;var firstChar=textAfter&&textAfter.charAt(0),lexical=state.lexical;lexical.type=="stat"&&firstChar=="}"&&(lexical=lexical.prev);var type=lexical.type,closing=firstChar==type;return type=="vardef"?lexical.indented+4:type=="form"&&firstChar=="{"?lexical.indented:type=="stat"||type=="form"?lexical.indented+indentUnit:lexical.info=="switch"&&!closing?lexical.indented+(/^(?:case|default)\b/.test(textAfter)?indentUnit:2*indentUnit):lexical.align?lexical.column+(closing?0:1):lexical.indented+(closing?0:indentUnit)},electricChars:":{}"}}),CodeMirror.defineMIME("text/javascript","javascript"),CodeMirror.defineMIME("application/json",{name:"javascript",json:!0}) \ No newline at end of file