| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  | // CodeMirror, copyright (c) by Marijn Haverbeke and others
 | 
					
						
							| 
									
										
										
										
											2018-10-07 12:02:07 +02:00
										 |  |  | // Distributed under an MIT license: https://codemirror.net/LICENSE
 | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | (function(mod) { | 
					
						
							|  |  |  |   if (typeof exports == "object" && typeof module == "object") // CommonJS
 | 
					
						
							|  |  |  |     mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed")); | 
					
						
							|  |  |  |   else if (typeof define == "function" && define.amd) // AMD
 | 
					
						
							|  |  |  |     define(["../../lib/codemirror", "../htmlmixed/htmlmixed"], mod); | 
					
						
							|  |  |  |   else // Plain browser env
 | 
					
						
							|  |  |  |     mod(CodeMirror); | 
					
						
							|  |  |  | })(function(CodeMirror) { | 
					
						
							|  |  |  |   "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |   var paramData = { noEndTag: true, soyState: "param-def" }; | 
					
						
							|  |  |  |   var tags = { | 
					
						
							|  |  |  |     "alias": { noEndTag: true }, | 
					
						
							|  |  |  |     "delpackage": { noEndTag: true }, | 
					
						
							|  |  |  |     "namespace": { noEndTag: true, soyState: "namespace-def" }, | 
					
						
							| 
									
										
										
										
											2021-04-06 22:16:34 +02:00
										 |  |  |     "@attribute": paramData, | 
					
						
							|  |  |  |     "@attribute?": paramData, | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |     "@param": paramData, | 
					
						
							|  |  |  |     "@param?": paramData, | 
					
						
							|  |  |  |     "@inject": paramData, | 
					
						
							|  |  |  |     "@inject?": paramData, | 
					
						
							|  |  |  |     "@state": paramData, | 
					
						
							|  |  |  |     "template": { soyState: "templ-def", variableScope: true}, | 
					
						
							|  |  |  |     "literal": { }, | 
					
						
							|  |  |  |     "msg": {}, | 
					
						
							|  |  |  |     "fallbackmsg": { noEndTag: true, reduceIndent: true}, | 
					
						
							| 
									
										
										
										
											2019-08-25 18:46:32 +02:00
										 |  |  |     "select": {}, | 
					
						
							|  |  |  |     "plural": {}, | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |     "let": { soyState: "var-def" }, | 
					
						
							|  |  |  |     "if": {}, | 
					
						
							|  |  |  |     "elseif": { noEndTag: true, reduceIndent: true}, | 
					
						
							|  |  |  |     "else": { noEndTag: true, reduceIndent: true}, | 
					
						
							|  |  |  |     "switch": {}, | 
					
						
							|  |  |  |     "case": { noEndTag: true, reduceIndent: true}, | 
					
						
							|  |  |  |     "default": { noEndTag: true, reduceIndent: true}, | 
					
						
							| 
									
										
										
										
											2020-09-20 21:41:40 +02:00
										 |  |  |     "foreach": { variableScope: true, soyState: "for-loop" }, | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |     "ifempty": { noEndTag: true, reduceIndent: true}, | 
					
						
							| 
									
										
										
										
											2020-09-20 21:41:40 +02:00
										 |  |  |     "for": { variableScope: true, soyState: "for-loop" }, | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |     "call": { soyState: "templ-ref" }, | 
					
						
							|  |  |  |     "param": { soyState: "param-ref"}, | 
					
						
							|  |  |  |     "print": { noEndTag: true }, | 
					
						
							|  |  |  |     "deltemplate": { soyState: "templ-def", variableScope: true}, | 
					
						
							|  |  |  |     "delcall": { soyState: "templ-ref" }, | 
					
						
							|  |  |  |     "log": {}, | 
					
						
							|  |  |  |     "element": { variableScope: true }, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   var indentingTags = Object.keys(tags).filter(function(tag) { | 
					
						
							|  |  |  |     return !tags[tag].noEndTag || tags[tag].reduceIndent; | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |   CodeMirror.defineMode("soy", function(config) { | 
					
						
							|  |  |  |     var textMode = CodeMirror.getMode(config, "text/plain"); | 
					
						
							|  |  |  |     var modes = { | 
					
						
							| 
									
										
										
										
											2021-04-06 22:16:34 +02:00
										 |  |  |       html: CodeMirror.getMode(config, {name: "text/html", multilineTagIndentFactor: 2, multilineTagIndentPastTag: false, allowMissingTagName: true}), | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |       attributes: textMode, | 
					
						
							|  |  |  |       text: textMode, | 
					
						
							|  |  |  |       uri: textMode, | 
					
						
							| 
									
										
										
										
											2018-08-15 11:25:30 +02:00
										 |  |  |       trusted_resource_uri: textMode, | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |       css: CodeMirror.getMode(config, "text/css"), | 
					
						
							|  |  |  |       js: CodeMirror.getMode(config, {name: "text/javascript", statementIndent: 2 * config.indentUnit}) | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function last(array) { | 
					
						
							|  |  |  |       return array[array.length - 1]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function tokenUntil(stream, state, untilRegExp) { | 
					
						
							|  |  |  |       if (stream.sol()) { | 
					
						
							|  |  |  |         for (var indent = 0; indent < state.indent; indent++) { | 
					
						
							|  |  |  |           if (!stream.eat(/\s/)) break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (indent) return null; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       var oldString = stream.string; | 
					
						
							|  |  |  |       var match = untilRegExp.exec(oldString.substr(stream.pos)); | 
					
						
							|  |  |  |       if (match) { | 
					
						
							|  |  |  |         // We don't use backUp because it backs up just the position, not the state.
 | 
					
						
							|  |  |  |         // This uses an undocumented API.
 | 
					
						
							|  |  |  |         stream.string = oldString.substr(0, stream.pos + match.index); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       var result = stream.hideFirstChars(state.indent, function() { | 
					
						
							|  |  |  |         var localState = last(state.localStates); | 
					
						
							|  |  |  |         return localState.mode.token(stream, localState.state); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       stream.string = oldString; | 
					
						
							|  |  |  |       return result; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function contains(list, element) { | 
					
						
							|  |  |  |       while (list) { | 
					
						
							|  |  |  |         if (list.element === element) return true; | 
					
						
							|  |  |  |         list = list.next; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function prepend(list, element) { | 
					
						
							|  |  |  |       return { | 
					
						
							|  |  |  |         element: element, | 
					
						
							|  |  |  |         next: list | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |     function popcontext(state) { | 
					
						
							|  |  |  |       if (!state.context) return; | 
					
						
							|  |  |  |       if (state.context.scope) { | 
					
						
							|  |  |  |         state.variables = state.context.scope; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       state.context = state.context.previousContext; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |     // Reference a variable `name` in `list`.
 | 
					
						
							|  |  |  |     // Let `loose` be truthy to ignore missing identifiers.
 | 
					
						
							|  |  |  |     function ref(list, name, loose) { | 
					
						
							|  |  |  |       return contains(list, name) ? "variable-2" : (loose ? "variable" : "variable-2 error"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |     // Data for an open soy tag.
 | 
					
						
							|  |  |  |     function Context(previousContext, tag, scope) { | 
					
						
							|  |  |  |       this.previousContext = previousContext; | 
					
						
							|  |  |  |       this.tag = tag; | 
					
						
							|  |  |  |       this.kind = null; | 
					
						
							|  |  |  |       this.scope = scope; | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-20 21:41:40 +02:00
										 |  |  |     function expression(stream, state) { | 
					
						
							|  |  |  |       var match; | 
					
						
							|  |  |  |       if (stream.match(/[[]/)) { | 
					
						
							|  |  |  |         state.soyState.push("list-literal"); | 
					
						
							|  |  |  |         state.context = new Context(state.context, "list-literal", state.variables); | 
					
						
							|  |  |  |         state.lookupVariables = false; | 
					
						
							|  |  |  |         return null; | 
					
						
							|  |  |  |       } else if (stream.match(/map\b/)) { | 
					
						
							|  |  |  |         state.soyState.push("map-literal"); | 
					
						
							|  |  |  |         return "keyword"; | 
					
						
							|  |  |  |       } else if (stream.match(/record\b/)) { | 
					
						
							|  |  |  |         state.soyState.push("record-literal"); | 
					
						
							|  |  |  |         return "keyword"; | 
					
						
							|  |  |  |       } else if (stream.match(/([\w]+)(?=\()/)) { | 
					
						
							|  |  |  |         return "variable callee"; | 
					
						
							|  |  |  |       } else if (match = stream.match(/^["']/)) { | 
					
						
							|  |  |  |         state.soyState.push("string"); | 
					
						
							|  |  |  |         state.quoteKind = match[0]; | 
					
						
							|  |  |  |         return "string"; | 
					
						
							|  |  |  |       } else if (stream.match(/^[(]/)) { | 
					
						
							|  |  |  |         state.soyState.push("open-parentheses"); | 
					
						
							|  |  |  |         return null; | 
					
						
							|  |  |  |       } else if (stream.match(/(null|true|false)(?!\w)/) || | 
					
						
							|  |  |  |           stream.match(/0x([0-9a-fA-F]{2,})/) || | 
					
						
							|  |  |  |           stream.match(/-?([0-9]*[.])?[0-9]+(e[0-9]*)?/)) { | 
					
						
							|  |  |  |         return "atom"; | 
					
						
							|  |  |  |       } else if (stream.match(/(\||[+\-*\/%]|[=!]=|\?:|[<>]=?)/)) { | 
					
						
							|  |  |  |         // Tokenize filter, binary, null propagator, and equality operators.
 | 
					
						
							|  |  |  |         return "operator"; | 
					
						
							|  |  |  |       } else if (match = stream.match(/^\$([\w]+)/)) { | 
					
						
							|  |  |  |         return ref(state.variables, match[1], !state.lookupVariables); | 
					
						
							|  |  |  |       } else if (match = stream.match(/^\w+/)) { | 
					
						
							|  |  |  |         return /^(?:as|and|or|not|in|if)$/.test(match[0]) ? "keyword" : null; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       stream.next(); | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |     return { | 
					
						
							|  |  |  |       startState: function() { | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |           soyState: [], | 
					
						
							|  |  |  |           variables: prepend(null, 'ij'), | 
					
						
							|  |  |  |           scopes: null, | 
					
						
							|  |  |  |           indent: 0, | 
					
						
							|  |  |  |           quoteKind: null, | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |           context: null, | 
					
						
							| 
									
										
										
										
											2020-09-20 21:41:40 +02:00
										 |  |  |           lookupVariables: true, // Is unknown variables considered an error
 | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |           localStates: [{ | 
					
						
							|  |  |  |             mode: modes.html, | 
					
						
							|  |  |  |             state: CodeMirror.startState(modes.html) | 
					
						
							|  |  |  |           }] | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       copyState: function(state) { | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |           tag: state.tag, // Last seen Soy tag.
 | 
					
						
							|  |  |  |           soyState: state.soyState.concat([]), | 
					
						
							|  |  |  |           variables: state.variables, | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |           context: state.context, | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |           indent: state.indent, // Indentation of the following line.
 | 
					
						
							|  |  |  |           quoteKind: state.quoteKind, | 
					
						
							| 
									
										
										
										
											2020-09-20 21:41:40 +02:00
										 |  |  |           lookupVariables: state.lookupVariables, | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |           localStates: state.localStates.map(function(localState) { | 
					
						
							|  |  |  |             return { | 
					
						
							|  |  |  |               mode: localState.mode, | 
					
						
							|  |  |  |               state: CodeMirror.copyState(localState.mode, localState.state) | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       token: function(stream, state) { | 
					
						
							|  |  |  |         var match; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         switch (last(state.soyState)) { | 
					
						
							|  |  |  |           case "comment": | 
					
						
							|  |  |  |             if (stream.match(/^.*?\*\//)) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |               stream.skipToEnd(); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |             if (!state.context || !state.context.scope) { | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |               var paramRe = /@param\??\s+(\S+)/g; | 
					
						
							|  |  |  |               var current = stream.current(); | 
					
						
							|  |  |  |               for (var match; (match = paramRe.exec(current)); ) { | 
					
						
							|  |  |  |                 state.variables = prepend(state.variables, match[1]); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return "comment"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           case "string": | 
					
						
							|  |  |  |             var match = stream.match(/^.*?(["']|\\[\s\S])/); | 
					
						
							|  |  |  |             if (!match) { | 
					
						
							|  |  |  |               stream.skipToEnd(); | 
					
						
							|  |  |  |             } else if (match[1] == state.quoteKind) { | 
					
						
							|  |  |  |               state.quoteKind = null; | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return "string"; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-15 11:25:30 +02:00
										 |  |  |         if (!state.soyState.length || last(state.soyState) != "literal") { | 
					
						
							|  |  |  |           if (stream.match(/^\/\*/)) { | 
					
						
							|  |  |  |             state.soyState.push("comment"); | 
					
						
							|  |  |  |             return "comment"; | 
					
						
							|  |  |  |           } else if (stream.match(stream.sol() ? /^\s*\/\/.*/ : /^\s+\/\/.*/)) { | 
					
						
							|  |  |  |             return "comment"; | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         switch (last(state.soyState)) { | 
					
						
							|  |  |  |           case "templ-def": | 
					
						
							|  |  |  |             if (match = stream.match(/^\.?([\w]+(?!\.[\w]+)*)/)) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return "def"; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             stream.next(); | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           case "templ-ref": | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |             if (match = stream.match(/(\.?[a-zA-Z_][a-zA-Z_0-9]+)+/)) { | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |               state.soyState.pop(); | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |               // If the first character is '.', it can only be a local template.
 | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |               if (match[0][0] == '.') { | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |                 return "variable-2" | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |               } | 
					
						
							|  |  |  |               // Otherwise
 | 
					
						
							|  |  |  |               return "variable"; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-09-20 21:41:40 +02:00
										 |  |  |             if (match = stream.match(/^\$([\w]+)/)) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return ref(state.variables, match[1], !state.lookupVariables); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |             stream.next(); | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |           case "namespace-def": | 
					
						
							|  |  |  |             if (match = stream.match(/^\.?([\w\.]+)/)) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return "variable"; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             stream.next(); | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |           case "param-def": | 
					
						
							| 
									
										
										
										
											2021-04-06 22:16:34 +02:00
										 |  |  |             if (match = stream.match(/^\*/)) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               state.soyState.push("param-type"); | 
					
						
							|  |  |  |               return "type"; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |             if (match = stream.match(/^\w+/)) { | 
					
						
							|  |  |  |               state.variables = prepend(state.variables, match[0]); | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               state.soyState.push("param-type"); | 
					
						
							|  |  |  |               return "def"; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             stream.next(); | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |           case "param-ref": | 
					
						
							|  |  |  |             if (match = stream.match(/^\w+/)) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return "property"; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             stream.next(); | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-20 21:41:40 +02:00
										 |  |  |           case "open-parentheses": | 
					
						
							|  |  |  |             if (stream.match(/[)]/)) { | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-09-20 21:41:40 +02:00
										 |  |  |             return expression(stream, state); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           case "param-type": | 
					
						
							|  |  |  |             var peekChar = stream.peek(); | 
					
						
							|  |  |  |             if ("}]=>,".indexOf(peekChar) != -1) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } else if (peekChar == "[") { | 
					
						
							|  |  |  |               state.soyState.push('param-type-record'); | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } else if (peekChar == "(") { | 
					
						
							|  |  |  |               state.soyState.push('param-type-template'); | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } else if (peekChar == "<") { | 
					
						
							|  |  |  |               state.soyState.push('param-type-parameter'); | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } else if (match = stream.match(/^([\w]+|[?])/)) { | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |               return "type"; | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |             } | 
					
						
							|  |  |  |             stream.next(); | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-20 21:41:40 +02:00
										 |  |  |           case "param-type-record": | 
					
						
							|  |  |  |             var peekChar = stream.peek(); | 
					
						
							|  |  |  |             if (peekChar == "]") { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (stream.match(/^\w+/)) { | 
					
						
							|  |  |  |               state.soyState.push('param-type'); | 
					
						
							|  |  |  |               return "property"; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             stream.next(); | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           case "param-type-parameter": | 
					
						
							|  |  |  |             if (stream.match(/^[>]/)) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (stream.match(/^[<,]/)) { | 
					
						
							|  |  |  |               state.soyState.push('param-type'); | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             stream.next(); | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           case "param-type-template": | 
					
						
							|  |  |  |             if (stream.match(/[>]/)) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               state.soyState.push('param-type'); | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (stream.match(/^\w+/)) { | 
					
						
							|  |  |  |               state.soyState.push('param-type'); | 
					
						
							|  |  |  |               return "def"; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             stream.next(); | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |           case "var-def": | 
					
						
							|  |  |  |             if (match = stream.match(/^\$([\w]+)/)) { | 
					
						
							|  |  |  |               state.variables = prepend(state.variables, match[1]); | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return "def"; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             stream.next(); | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-20 21:41:40 +02:00
										 |  |  |           case "for-loop": | 
					
						
							|  |  |  |             if (stream.match(/\bin\b/)) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return "keyword"; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (stream.peek() == "$") { | 
					
						
							|  |  |  |               state.soyState.push('var-def'); | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             stream.next(); | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           case "record-literal": | 
					
						
							|  |  |  |             if (stream.match(/^[)]/)) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (stream.match(/[(,]/)) { | 
					
						
							|  |  |  |               state.soyState.push("map-value") | 
					
						
							|  |  |  |               state.soyState.push("record-key") | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             stream.next() | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           case "map-literal": | 
					
						
							|  |  |  |             if (stream.match(/^[)]/)) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (stream.match(/[(,]/)) { | 
					
						
							|  |  |  |               state.soyState.push("map-value") | 
					
						
							|  |  |  |               state.soyState.push("map-value") | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             stream.next() | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           case "list-literal": | 
					
						
							| 
									
										
										
										
											2021-04-06 22:16:34 +02:00
										 |  |  |             if (stream.match(']')) { | 
					
						
							| 
									
										
										
										
											2020-09-20 21:41:40 +02:00
										 |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               state.lookupVariables = true; | 
					
						
							|  |  |  |               popcontext(state); | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (stream.match(/\bfor\b/)) { | 
					
						
							|  |  |  |               state.lookupVariables = true; | 
					
						
							|  |  |  |               state.soyState.push('for-loop'); | 
					
						
							|  |  |  |               return "keyword"; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return expression(stream, state); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           case "record-key": | 
					
						
							|  |  |  |             if (stream.match(/[\w]+/)) { | 
					
						
							|  |  |  |               return "property"; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (stream.match(/^[:]/)) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             stream.next(); | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           case "map-value": | 
					
						
							|  |  |  |             if (stream.peek() == ")" || stream.peek() == "," || stream.match(/^[:)]/)) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return expression(stream, state); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           case "import": | 
					
						
							|  |  |  |             if (stream.eat(";")) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               state.indent -= 2 * config.indentUnit; | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (stream.match(/\w+(?=\s+as)/)) { | 
					
						
							|  |  |  |               return "variable"; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (match = stream.match(/\w+/)) { | 
					
						
							|  |  |  |               return /(from|as)/.test(match[0]) ? "keyword" : "def"; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (match = stream.match(/^["']/)) { | 
					
						
							|  |  |  |               state.soyState.push("string"); | 
					
						
							|  |  |  |               state.quoteKind = match[0]; | 
					
						
							|  |  |  |               return "string"; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             stream.next(); | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |           case "tag": | 
					
						
							| 
									
										
										
										
											2021-04-06 22:16:34 +02:00
										 |  |  |             var endTag; | 
					
						
							|  |  |  |             var tagName; | 
					
						
							|  |  |  |             if (state.tag === undefined) { | 
					
						
							|  |  |  |               endTag = true; | 
					
						
							|  |  |  |               tagName = ''; | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |               endTag = state.tag[0] == "/"; | 
					
						
							|  |  |  |               tagName = endTag ? state.tag.substring(1) : state.tag; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |             var tag = tags[tagName]; | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |             if (stream.match(/^\/?}/)) { | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |               var selfClosed = stream.current() == "/}"; | 
					
						
							|  |  |  |               if (selfClosed && !endTag) { | 
					
						
							|  |  |  |                 popcontext(state); | 
					
						
							|  |  |  |               } | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |               if (state.tag == "/template" || state.tag == "/deltemplate") { | 
					
						
							|  |  |  |                 state.variables = prepend(null, 'ij'); | 
					
						
							|  |  |  |                 state.indent = 0; | 
					
						
							|  |  |  |               } else { | 
					
						
							|  |  |  |                 state.indent -= config.indentUnit * | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |                     (selfClosed || indentingTags.indexOf(state.tag) == -1 ? 2 : 1); | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |               } | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return "keyword"; | 
					
						
							|  |  |  |             } else if (stream.match(/^([\w?]+)(?==)/)) { | 
					
						
							| 
									
										
										
										
											2020-09-20 21:41:40 +02:00
										 |  |  |               if (state.context && state.context.tag == tagName && stream.current() == "kind" && (match = stream.match(/^="([^"]+)/, false))) { | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |                 var kind = match[1]; | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |                 state.context.kind = kind; | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |                 var mode = modes[kind] || modes.html; | 
					
						
							|  |  |  |                 var localState = last(state.localStates); | 
					
						
							|  |  |  |                 if (localState.mode.indent) { | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |                   state.indent += localState.mode.indent(localState.state, "", ""); | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |                 } | 
					
						
							|  |  |  |                 state.localStates.push({ | 
					
						
							|  |  |  |                   mode: mode, | 
					
						
							|  |  |  |                   state: CodeMirror.startState(mode) | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |               return "attribute"; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-09-20 21:41:40 +02:00
										 |  |  |             return expression(stream, state); | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-06 22:16:34 +02:00
										 |  |  |           case "template-call-expression": | 
					
						
							|  |  |  |             if (stream.match(/^([\w-?]+)(?==)/)) { | 
					
						
							|  |  |  |               return "attribute"; | 
					
						
							|  |  |  |             } else if (stream.eat('>')) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return "keyword"; | 
					
						
							|  |  |  |             } else if (stream.eat('/>')) { | 
					
						
							|  |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return "keyword"; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return expression(stream, state); | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |           case "literal": | 
					
						
							| 
									
										
										
										
											2021-04-06 22:16:34 +02:00
										 |  |  |             if (stream.match('{/literal}', false)) { | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |               state.soyState.pop(); | 
					
						
							|  |  |  |               return this.token(stream, state); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return tokenUntil(stream, state, /\{\/literal}/); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-06 22:16:34 +02:00
										 |  |  |         if (stream.match('{literal}')) { | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |           state.indent += config.indentUnit; | 
					
						
							|  |  |  |           state.soyState.push("literal"); | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |           state.context = new Context(state.context, "literal", state.variables); | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |           return "keyword"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // A tag-keyword must be followed by whitespace, comment or a closing tag.
 | 
					
						
							| 
									
										
										
										
											2018-08-15 11:25:30 +02:00
										 |  |  |         } else if (match = stream.match(/^\{([/@\\]?\w+\??)(?=$|[\s}]|\/[/*])/)) { | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |           var prevTag = state.tag; | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |           state.tag = match[1]; | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |           var endTag = state.tag[0] == "/"; | 
					
						
							|  |  |  |           var indentingTag = !!tags[state.tag]; | 
					
						
							|  |  |  |           var tagName = endTag ? state.tag.substring(1) : state.tag; | 
					
						
							|  |  |  |           var tag = tags[tagName]; | 
					
						
							|  |  |  |           if (state.tag != "/switch") | 
					
						
							|  |  |  |             state.indent += ((endTag || tag && tag.reduceIndent) && prevTag != "switch" ? 1 : 2) * config.indentUnit; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |           state.soyState.push("tag"); | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |           var tagError = false; | 
					
						
							|  |  |  |           if (tag) { | 
					
						
							|  |  |  |             if (!endTag) { | 
					
						
							|  |  |  |               if (tag.soyState) state.soyState.push(tag.soyState); | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |             // If a new tag, open a new context.
 | 
					
						
							|  |  |  |             if (!tag.noEndTag && (indentingTag || !endTag)) { | 
					
						
							|  |  |  |               state.context = new Context(state.context, state.tag, tag.variableScope ? state.variables : null); | 
					
						
							|  |  |  |             // Otherwise close the current context.
 | 
					
						
							|  |  |  |             } else if (endTag) { | 
					
						
							|  |  |  |               if (!state.context || state.context.tag != tagName) { | 
					
						
							|  |  |  |                 tagError = true; | 
					
						
							|  |  |  |               } else if (state.context) { | 
					
						
							|  |  |  |                 if (state.context.kind) { | 
					
						
							|  |  |  |                   state.localStates.pop(); | 
					
						
							|  |  |  |                   var localState = last(state.localStates); | 
					
						
							|  |  |  |                   if (localState.mode.indent) { | 
					
						
							|  |  |  |                     state.indent -= localState.mode.indent(localState.state, "", ""); | 
					
						
							|  |  |  |                   } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 popcontext(state); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           } else if (endTag) { | 
					
						
							|  |  |  |             // Assume all tags with a closing tag are defined in the config.
 | 
					
						
							|  |  |  |             tagError = true; | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |           } | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |           return (tagError ? "error " : "") + "keyword"; | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Not a tag-keyword; it's an implicit print tag.
 | 
					
						
							|  |  |  |         } else if (stream.eat('{')) { | 
					
						
							|  |  |  |           state.tag = "print"; | 
					
						
							|  |  |  |           state.indent += 2 * config.indentUnit; | 
					
						
							|  |  |  |           state.soyState.push("tag"); | 
					
						
							|  |  |  |           return "keyword"; | 
					
						
							| 
									
										
										
										
											2020-09-20 21:41:40 +02:00
										 |  |  |         } else if (!state.context && stream.match(/\bimport\b/)) { | 
					
						
							|  |  |  |           state.soyState.push("import"); | 
					
						
							|  |  |  |           state.indent += 2 * config.indentUnit; | 
					
						
							|  |  |  |           return "keyword"; | 
					
						
							| 
									
										
										
										
											2021-04-06 22:16:34 +02:00
										 |  |  |         } else if (match = stream.match('<{')) { | 
					
						
							|  |  |  |           state.soyState.push("template-call-expression"); | 
					
						
							|  |  |  |           state.indent += 2 * config.indentUnit; | 
					
						
							|  |  |  |           state.soyState.push("tag"); | 
					
						
							|  |  |  |           return "keyword"; | 
					
						
							|  |  |  |         } else if (match = stream.match('</>')) { | 
					
						
							|  |  |  |           state.indent -= 1 * config.indentUnit; | 
					
						
							|  |  |  |           return "keyword"; | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return tokenUntil(stream, state, /\{|\s+\/\/|\/\*/); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |       indent: function(state, textAfter, line) { | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |         var indent = state.indent, top = last(state.soyState); | 
					
						
							|  |  |  |         if (top == "comment") return CodeMirror.Pass; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (top == "literal") { | 
					
						
							|  |  |  |           if (/^\{\/literal}/.test(textAfter)) indent -= config.indentUnit; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           if (/^\s*\{\/(template|deltemplate)\b/.test(textAfter)) return 0; | 
					
						
							|  |  |  |           if (/^\{(\/|(fallbackmsg|elseif|else|ifempty)\b)/.test(textAfter)) indent -= config.indentUnit; | 
					
						
							|  |  |  |           if (state.tag != "switch" && /^\{(case|default)\b/.test(textAfter)) indent -= config.indentUnit; | 
					
						
							|  |  |  |           if (/^\{\/switch\b/.test(textAfter)) indent -= config.indentUnit; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         var localState = last(state.localStates); | 
					
						
							|  |  |  |         if (indent && localState.mode.indent) { | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |           indent += localState.mode.indent(localState.state, textAfter, line); | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |         } | 
					
						
							|  |  |  |         return indent; | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       innerMode: function(state) { | 
					
						
							|  |  |  |         if (state.soyState.length && last(state.soyState) != "literal") return null; | 
					
						
							|  |  |  |         else return last(state.localStates); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       electricInput: /^\s*\{(\/|\/template|\/deltemplate|\/switch|fallbackmsg|elseif|else|case|default|ifempty|\/literal\})$/, | 
					
						
							|  |  |  |       lineComment: "//", | 
					
						
							|  |  |  |       blockCommentStart: "/*", | 
					
						
							|  |  |  |       blockCommentEnd: "*/", | 
					
						
							|  |  |  |       blockCommentContinue: " * ", | 
					
						
							|  |  |  |       useInnerComments: false, | 
					
						
							|  |  |  |       fold: "indent" | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   }, "htmlmixed"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-02 09:59:07 +02:00
										 |  |  |   CodeMirror.registerHelper("wordChars", "soy", /[\w$]/); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   CodeMirror.registerHelper("hintWords", "soy", Object.keys(tags).concat( | 
					
						
							|  |  |  |       ["css", "debugger"])); | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |   CodeMirror.defineMIME("text/x-soy", "soy"); | 
					
						
							|  |  |  | }); |