File modules/editor/codemirror/mode/soy/soy.js
Last commit: Sun Dec 17 01:14:09 2017 +0100 Jan Dankert Integration eines weiteren Code-Editors: Codemirror. Demnächst müssen wir hier mal aufräumen und andere Editoren rauswerfen.
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 // Distributed under an MIT license: http://codemirror.net/LICENSE 3 4 (function(mod) { 5 if (typeof exports == "object" && typeof module == "object") // CommonJS 6 mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed")); 7 else if (typeof define == "function" && define.amd) // AMD 8 define(["../../lib/codemirror", "../htmlmixed/htmlmixed"], mod); 9 else // Plain browser env 10 mod(CodeMirror); 11 })(function(CodeMirror) { 12 "use strict"; 13 14 var indentingTags = ["template", "literal", "msg", "fallbackmsg", "let", "if", "elseif", 15 "else", "switch", "case", "default", "foreach", "ifempty", "for", 16 "call", "param", "deltemplate", "delcall", "log"]; 17 18 CodeMirror.defineMode("soy", function(config) { 19 var textMode = CodeMirror.getMode(config, "text/plain"); 20 var modes = { 21 html: CodeMirror.getMode(config, {name: "text/html", multilineTagIndentFactor: 2, multilineTagIndentPastTag: false}), 22 attributes: textMode, 23 text: textMode, 24 uri: textMode, 25 css: CodeMirror.getMode(config, "text/css"), 26 js: CodeMirror.getMode(config, {name: "text/javascript", statementIndent: 2 * config.indentUnit}) 27 }; 28 29 function last(array) { 30 return array[array.length - 1]; 31 } 32 33 function tokenUntil(stream, state, untilRegExp) { 34 if (stream.sol()) { 35 for (var indent = 0; indent < state.indent; indent++) { 36 if (!stream.eat(/\s/)) break; 37 } 38 if (indent) return null; 39 } 40 var oldString = stream.string; 41 var match = untilRegExp.exec(oldString.substr(stream.pos)); 42 if (match) { 43 // We don't use backUp because it backs up just the position, not the state. 44 // This uses an undocumented API. 45 stream.string = oldString.substr(0, stream.pos + match.index); 46 } 47 var result = stream.hideFirstChars(state.indent, function() { 48 var localState = last(state.localStates); 49 return localState.mode.token(stream, localState.state); 50 }); 51 stream.string = oldString; 52 return result; 53 } 54 55 function contains(list, element) { 56 while (list) { 57 if (list.element === element) return true; 58 list = list.next; 59 } 60 return false; 61 } 62 63 function prepend(list, element) { 64 return { 65 element: element, 66 next: list 67 }; 68 } 69 70 // Reference a variable `name` in `list`. 71 // Let `loose` be truthy to ignore missing identifiers. 72 function ref(list, name, loose) { 73 return contains(list, name) ? "variable-2" : (loose ? "variable" : "variable-2 error"); 74 } 75 76 function popscope(state) { 77 if (state.scopes) { 78 state.variables = state.scopes.element; 79 state.scopes = state.scopes.next; 80 } 81 } 82 83 return { 84 startState: function() { 85 return { 86 kind: [], 87 kindTag: [], 88 soyState: [], 89 templates: null, 90 variables: prepend(null, 'ij'), 91 scopes: null, 92 indent: 0, 93 quoteKind: null, 94 localStates: [{ 95 mode: modes.html, 96 state: CodeMirror.startState(modes.html) 97 }] 98 }; 99 }, 100 101 copyState: function(state) { 102 return { 103 tag: state.tag, // Last seen Soy tag. 104 kind: state.kind.concat([]), // Values of kind="" attributes. 105 kindTag: state.kindTag.concat([]), // Opened tags with kind="" attributes. 106 soyState: state.soyState.concat([]), 107 templates: state.templates, 108 variables: state.variables, 109 scopes: state.scopes, 110 indent: state.indent, // Indentation of the following line. 111 quoteKind: state.quoteKind, 112 localStates: state.localStates.map(function(localState) { 113 return { 114 mode: localState.mode, 115 state: CodeMirror.copyState(localState.mode, localState.state) 116 }; 117 }) 118 }; 119 }, 120 121 token: function(stream, state) { 122 var match; 123 124 switch (last(state.soyState)) { 125 case "comment": 126 if (stream.match(/^.*?\*\//)) { 127 state.soyState.pop(); 128 } else { 129 stream.skipToEnd(); 130 } 131 if (!state.scopes) { 132 var paramRe = /@param\??\s+(\S+)/g; 133 var current = stream.current(); 134 for (var match; (match = paramRe.exec(current)); ) { 135 state.variables = prepend(state.variables, match[1]); 136 } 137 } 138 return "comment"; 139 140 case "string": 141 var match = stream.match(/^.*?(["']|\\[\s\S])/); 142 if (!match) { 143 stream.skipToEnd(); 144 } else if (match[1] == state.quoteKind) { 145 state.quoteKind = null; 146 state.soyState.pop(); 147 } 148 return "string"; 149 } 150 151 if (stream.match(/^\/\*/)) { 152 state.soyState.push("comment"); 153 return "comment"; 154 } else if (stream.match(stream.sol() || (state.soyState.length && last(state.soyState) != "literal") ? /^\s*\/\/.*/ : /^\s+\/\/.*/)) { 155 return "comment"; 156 } 157 158 switch (last(state.soyState)) { 159 case "templ-def": 160 if (match = stream.match(/^\.?([\w]+(?!\.[\w]+)*)/)) { 161 state.templates = prepend(state.templates, match[1]); 162 state.scopes = prepend(state.scopes, state.variables); 163 state.soyState.pop(); 164 return "def"; 165 } 166 stream.next(); 167 return null; 168 169 case "templ-ref": 170 if (match = stream.match(/^\.?([\w]+)/)) { 171 state.soyState.pop(); 172 // If the first character is '.', try to match against a local template name. 173 if (match[0][0] == '.') { 174 return ref(state.templates, match[1], true); 175 } 176 // Otherwise 177 return "variable"; 178 } 179 stream.next(); 180 return null; 181 182 case "param-def": 183 if (match = stream.match(/^\w+/)) { 184 state.variables = prepend(state.variables, match[0]); 185 state.soyState.pop(); 186 state.soyState.push("param-type"); 187 return "def"; 188 } 189 stream.next(); 190 return null; 191 192 case "param-type": 193 if (stream.peek() == "}") { 194 state.soyState.pop(); 195 return null; 196 } 197 if (stream.eatWhile(/^[\w]+/)) { 198 return "variable-3"; 199 } 200 stream.next(); 201 return null; 202 203 case "var-def": 204 if (match = stream.match(/^\$([\w]+)/)) { 205 state.variables = prepend(state.variables, match[1]); 206 state.soyState.pop(); 207 return "def"; 208 } 209 stream.next(); 210 return null; 211 212 case "tag": 213 if (stream.match(/^\/?}/)) { 214 if (state.tag == "/template" || state.tag == "/deltemplate") { 215 popscope(state); 216 state.variables = prepend(null, 'ij'); 217 state.indent = 0; 218 } else { 219 if (state.tag == "/for" || state.tag == "/foreach") { 220 popscope(state); 221 } 222 state.indent -= config.indentUnit * 223 (stream.current() == "/}" || indentingTags.indexOf(state.tag) == -1 ? 2 : 1); 224 } 225 state.soyState.pop(); 226 return "keyword"; 227 } else if (stream.match(/^([\w?]+)(?==)/)) { 228 if (stream.current() == "kind" && (match = stream.match(/^="([^"]+)/, false))) { 229 var kind = match[1]; 230 state.kind.push(kind); 231 state.kindTag.push(state.tag); 232 var mode = modes[kind] || modes.html; 233 var localState = last(state.localStates); 234 if (localState.mode.indent) { 235 state.indent += localState.mode.indent(localState.state, ""); 236 } 237 state.localStates.push({ 238 mode: mode, 239 state: CodeMirror.startState(mode) 240 }); 241 } 242 return "attribute"; 243 } else if (match = stream.match(/^["']/)) { 244 state.soyState.push("string"); 245 state.quoteKind = match; 246 return "string"; 247 } 248 if (match = stream.match(/^\$([\w]+)/)) { 249 return ref(state.variables, match[1]); 250 } 251 if (match = stream.match(/^\w+/)) { 252 return /^(?:as|and|or|not|in)$/.test(match[0]) ? "keyword" : null; 253 } 254 stream.next(); 255 return null; 256 257 case "literal": 258 if (stream.match(/^(?=\{\/literal})/)) { 259 state.indent -= config.indentUnit; 260 state.soyState.pop(); 261 return this.token(stream, state); 262 } 263 return tokenUntil(stream, state, /\{\/literal}/); 264 } 265 266 if (stream.match(/^\{literal}/)) { 267 state.indent += config.indentUnit; 268 state.soyState.push("literal"); 269 return "keyword"; 270 271 // A tag-keyword must be followed by whitespace, comment or a closing tag. 272 } else if (match = stream.match(/^\{([\/@\\]?\w+\??)(?=[\s\}]|\/[/*])/)) { 273 if (match[1] != "/switch") 274 state.indent += (/^(\/|(else|elseif|ifempty|case|fallbackmsg|default)$)/.test(match[1]) && state.tag != "switch" ? 1 : 2) * config.indentUnit; 275 state.tag = match[1]; 276 if (state.tag == "/" + last(state.kindTag)) { 277 // We found the tag that opened the current kind="". 278 state.kind.pop(); 279 state.kindTag.pop(); 280 state.localStates.pop(); 281 var localState = last(state.localStates); 282 if (localState.mode.indent) { 283 state.indent -= localState.mode.indent(localState.state, ""); 284 } 285 } 286 state.soyState.push("tag"); 287 if (state.tag == "template" || state.tag == "deltemplate") { 288 state.soyState.push("templ-def"); 289 } else if (state.tag == "call" || state.tag == "delcall") { 290 state.soyState.push("templ-ref"); 291 } else if (state.tag == "let") { 292 state.soyState.push("var-def"); 293 } else if (state.tag == "for" || state.tag == "foreach") { 294 state.scopes = prepend(state.scopes, state.variables); 295 state.soyState.push("var-def"); 296 } else if (state.tag == "namespace") { 297 if (!state.scopes) { 298 state.variables = prepend(null, 'ij'); 299 } 300 } else if (state.tag.match(/^@(?:param\??|inject)/)) { 301 state.soyState.push("param-def"); 302 } 303 return "keyword"; 304 305 // Not a tag-keyword; it's an implicit print tag. 306 } else if (stream.eat('{')) { 307 state.tag = "print"; 308 state.indent += 2 * config.indentUnit; 309 state.soyState.push("tag"); 310 return "keyword"; 311 } 312 313 return tokenUntil(stream, state, /\{|\s+\/\/|\/\*/); 314 }, 315 316 indent: function(state, textAfter) { 317 var indent = state.indent, top = last(state.soyState); 318 if (top == "comment") return CodeMirror.Pass; 319 320 if (top == "literal") { 321 if (/^\{\/literal}/.test(textAfter)) indent -= config.indentUnit; 322 } else { 323 if (/^\s*\{\/(template|deltemplate)\b/.test(textAfter)) return 0; 324 if (/^\{(\/|(fallbackmsg|elseif|else|ifempty)\b)/.test(textAfter)) indent -= config.indentUnit; 325 if (state.tag != "switch" && /^\{(case|default)\b/.test(textAfter)) indent -= config.indentUnit; 326 if (/^\{\/switch\b/.test(textAfter)) indent -= config.indentUnit; 327 } 328 var localState = last(state.localStates); 329 if (indent && localState.mode.indent) { 330 indent += localState.mode.indent(localState.state, textAfter); 331 } 332 return indent; 333 }, 334 335 innerMode: function(state) { 336 if (state.soyState.length && last(state.soyState) != "literal") return null; 337 else return last(state.localStates); 338 }, 339 340 electricInput: /^\s*\{(\/|\/template|\/deltemplate|\/switch|fallbackmsg|elseif|else|case|default|ifempty|\/literal\})$/, 341 lineComment: "//", 342 blockCommentStart: "/*", 343 blockCommentEnd: "*/", 344 blockCommentContinue: " * ", 345 useInnerComments: false, 346 fold: "indent" 347 }; 348 }, "htmlmixed"); 349 350 CodeMirror.registerHelper("hintWords", "soy", indentingTags.concat( 351 ["delpackage", "namespace", "alias", "print", "css", "debugger"])); 352 353 CodeMirror.defineMIME("text/x-soy", "soy"); 354 });
Downloadmodules/editor/codemirror/mode/soy/soy.js
History Sun, 17 Dec 2017 01:14:09 +0100 Jan Dankert Integration eines weiteren Code-Editors: Codemirror. Demnächst müssen wir hier mal aufräumen und andere Editoren rauswerfen.