LaravelTest
665 строк · 22.5 Кб
1// CodeMirror, copyright (c) by Marijn Haverbeke and others
2// Distributed under an MIT license: https://codemirror.net/LICENSE
3
4(function(mod) {5if (typeof exports == "object" && typeof module == "object") // CommonJS6mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"));7else if (typeof define == "function" && define.amd) // AMD8define(["../../lib/codemirror", "../htmlmixed/htmlmixed"], mod);9else // Plain browser env10mod(CodeMirror);11})(function(CodeMirror) {12"use strict";13
14var paramData = { noEndTag: true, soyState: "param-def" };15var tags = {16"alias": { noEndTag: true },17"delpackage": { noEndTag: true },18"namespace": { noEndTag: true, soyState: "namespace-def" },19"@attribute": paramData,20"@attribute?": paramData,21"@param": paramData,22"@param?": paramData,23"@inject": paramData,24"@inject?": paramData,25"@state": paramData,26"template": { soyState: "templ-def", variableScope: true},27"extern": {soyState: "param-def"},28"export": {soyState: "export"},29"literal": { },30"msg": {},31"fallbackmsg": { noEndTag: true, reduceIndent: true},32"select": {},33"plural": {},34"let": { soyState: "var-def" },35"if": {},36"javaimpl": {},37"jsimpl": {},38"elseif": { noEndTag: true, reduceIndent: true},39"else": { noEndTag: true, reduceIndent: true},40"switch": {},41"case": { noEndTag: true, reduceIndent: true},42"default": { noEndTag: true, reduceIndent: true},43"foreach": { variableScope: true, soyState: "for-loop" },44"ifempty": { noEndTag: true, reduceIndent: true},45"for": { variableScope: true, soyState: "for-loop" },46"call": { soyState: "templ-ref" },47"param": { soyState: "param-ref"},48"print": { noEndTag: true },49"deltemplate": { soyState: "templ-def", variableScope: true},50"delcall": { soyState: "templ-ref" },51"log": {},52"element": { variableScope: true },53"velog": {},54"const": { soyState: "const-def"},55};56
57var indentingTags = Object.keys(tags).filter(function(tag) {58return !tags[tag].noEndTag || tags[tag].reduceIndent;59});60
61CodeMirror.defineMode("soy", function(config) {62var textMode = CodeMirror.getMode(config, "text/plain");63var modes = {64html: CodeMirror.getMode(config, {name: "text/html", multilineTagIndentFactor: 2, multilineTagIndentPastTag: false, allowMissingTagName: true}),65attributes: textMode,66text: textMode,67uri: textMode,68trusted_resource_uri: textMode,69css: CodeMirror.getMode(config, "text/css"),70js: CodeMirror.getMode(config, {name: "text/javascript", statementIndent: 2 * config.indentUnit})71};72
73function last(array) {74return array[array.length - 1];75}76
77function tokenUntil(stream, state, untilRegExp) {78if (stream.sol()) {79for (var indent = 0; indent < state.indent; indent++) {80if (!stream.eat(/\s/)) break;81}82if (indent) return null;83}84var oldString = stream.string;85var match = untilRegExp.exec(oldString.substr(stream.pos));86if (match) {87// We don't use backUp because it backs up just the position, not the state.88// This uses an undocumented API.89stream.string = oldString.substr(0, stream.pos + match.index);90}91var result = stream.hideFirstChars(state.indent, function() {92var localState = last(state.localStates);93return localState.mode.token(stream, localState.state);94});95stream.string = oldString;96return result;97}98
99function contains(list, element) {100while (list) {101if (list.element === element) return true;102list = list.next;103}104return false;105}106
107function prepend(list, element) {108return {109element: element,110next: list111};112}113
114function popcontext(state) {115if (!state.context) return;116if (state.context.scope) {117state.variables = state.context.scope;118}119state.context = state.context.previousContext;120}121
122// Reference a variable `name` in `list`.123// Let `loose` be truthy to ignore missing identifiers.124function ref(list, name, loose) {125return contains(list, name) ? "variable-2" : (loose ? "variable" : "variable-2 error");126}127
128// Data for an open soy tag.129function Context(previousContext, tag, scope) {130this.previousContext = previousContext;131this.tag = tag;132this.kind = null;133this.scope = scope;134}135
136function expression(stream, state) {137var match;138if (stream.match(/[[]/)) {139state.soyState.push("list-literal");140state.context = new Context(state.context, "list-literal", state.variables);141state.lookupVariables = false;142return null;143} else if (stream.match(/\bmap(?=\()/)) {144state.soyState.push("map-literal");145return "keyword";146} else if (stream.match(/\brecord(?=\()/)) {147state.soyState.push("record-literal");148return "keyword";149} else if (stream.match(/([\w]+)(?=\()/)) {150return "variable callee";151} else if (match = stream.match(/^["']/)) {152state.soyState.push("string");153state.quoteKind = match[0];154return "string";155} else if (stream.match(/^[(]/)) {156state.soyState.push("open-parentheses");157return null;158} else if (stream.match(/(null|true|false)(?!\w)/) ||159stream.match(/0x([0-9a-fA-F]{2,})/) ||160stream.match(/-?([0-9]*[.])?[0-9]+(e[0-9]*)?/)) {161return "atom";162} else if (stream.match(/(\||[+\-*\/%]|[=!]=|\?:|[<>]=?)/)) {163// Tokenize filter, binary, null propagator, and equality operators.164return "operator";165} else if (match = stream.match(/^\$([\w]+)/)) {166return ref(state.variables, match[1], !state.lookupVariables);167} else if (match = stream.match(/^\w+/)) {168return /^(?:as|and|or|not|in|if)$/.test(match[0]) ? "keyword" : null;169}170
171stream.next();172return null;173}174
175return {176startState: function() {177return {178soyState: [],179variables: prepend(null, 'ij'),180scopes: null,181indent: 0,182quoteKind: null,183context: null,184lookupVariables: true, // Is unknown variables considered an error185localStates: [{186mode: modes.html,187state: CodeMirror.startState(modes.html)188}]189};190},191
192copyState: function(state) {193return {194tag: state.tag, // Last seen Soy tag.195soyState: state.soyState.concat([]),196variables: state.variables,197context: state.context,198indent: state.indent, // Indentation of the following line.199quoteKind: state.quoteKind,200lookupVariables: state.lookupVariables,201localStates: state.localStates.map(function(localState) {202return {203mode: localState.mode,204state: CodeMirror.copyState(localState.mode, localState.state)205};206})207};208},209
210token: function(stream, state) {211var match;212
213switch (last(state.soyState)) {214case "comment":215if (stream.match(/^.*?\*\//)) {216state.soyState.pop();217} else {218stream.skipToEnd();219}220if (!state.context || !state.context.scope) {221var paramRe = /@param\??\s+(\S+)/g;222var current = stream.current();223for (var match; (match = paramRe.exec(current)); ) {224state.variables = prepend(state.variables, match[1]);225}226}227return "comment";228
229case "string":230var match = stream.match(/^.*?(["']|\\[\s\S])/);231if (!match) {232stream.skipToEnd();233} else if (match[1] == state.quoteKind) {234state.quoteKind = null;235state.soyState.pop();236}237return "string";238}239
240if (!state.soyState.length || last(state.soyState) != "literal") {241if (stream.match(/^\/\*/)) {242state.soyState.push("comment");243return "comment";244} else if (stream.match(stream.sol() ? /^\s*\/\/.*/ : /^\s+\/\/.*/)) {245return "comment";246}247}248
249switch (last(state.soyState)) {250case "templ-def":251if (match = stream.match(/^\.?([\w]+(?!\.[\w]+)*)/)) {252state.soyState.pop();253return "def";254}255stream.next();256return null;257
258case "templ-ref":259if (match = stream.match(/(\.?[a-zA-Z_][a-zA-Z_0-9]+)+/)) {260state.soyState.pop();261// If the first character is '.', it can only be a local template.262if (match[0][0] == '.') {263return "variable-2"264}265// Otherwise266return "variable";267}268if (match = stream.match(/^\$([\w]+)/)) {269state.soyState.pop();270return ref(state.variables, match[1], !state.lookupVariables);271}272
273stream.next();274return null;275
276case "namespace-def":277if (match = stream.match(/^\.?([\w\.]+)/)) {278state.soyState.pop();279return "variable";280}281stream.next();282return null;283
284case "param-def":285if (match = stream.match(/^\*/)) {286state.soyState.pop();287state.soyState.push("param-type");288return "type";289}290if (match = stream.match(/^\w+/)) {291state.variables = prepend(state.variables, match[0]);292state.soyState.pop();293state.soyState.push("param-type");294return "def";295}296stream.next();297return null;298
299case "param-ref":300if (match = stream.match(/^\w+/)) {301state.soyState.pop();302return "property";303}304stream.next();305return null;306
307case "open-parentheses":308if (stream.match(/[)]/)) {309state.soyState.pop();310return null;311}312return expression(stream, state);313
314case "param-type":315var peekChar = stream.peek();316if ("}]=>,".indexOf(peekChar) != -1) {317state.soyState.pop();318return null;319} else if (peekChar == "[") {320state.soyState.push('param-type-record');321return null;322} else if (peekChar == "(") {323state.soyState.push('param-type-template');324return null;325} else if (peekChar == "<") {326state.soyState.push('param-type-parameter');327return null;328} else if (match = stream.match(/^([\w]+|[?])/)) {329return "type";330}331stream.next();332return null;333
334case "param-type-record":335var peekChar = stream.peek();336if (peekChar == "]") {337state.soyState.pop();338return null;339}340if (stream.match(/^\w+/)) {341state.soyState.push('param-type');342return "property";343}344stream.next();345return null;346
347case "param-type-parameter":348if (stream.match(/^[>]/)) {349state.soyState.pop();350return null;351}352if (stream.match(/^[<,]/)) {353state.soyState.push('param-type');354return null;355}356stream.next();357return null;358
359case "param-type-template":360if (stream.match(/[>]/)) {361state.soyState.pop();362state.soyState.push('param-type');363return null;364}365if (stream.match(/^\w+/)) {366state.soyState.push('param-type');367return "def";368}369stream.next();370return null;371
372case "var-def":373if (match = stream.match(/^\$([\w]+)/)) {374state.variables = prepend(state.variables, match[1]);375state.soyState.pop();376return "def";377}378stream.next();379return null;380
381case "for-loop":382if (stream.match(/\bin\b/)) {383state.soyState.pop();384return "keyword";385}386if (stream.peek() == "$") {387state.soyState.push('var-def');388return null;389}390stream.next();391return null;392
393case "record-literal":394if (stream.match(/^[)]/)) {395state.soyState.pop();396return null;397}398if (stream.match(/[(,]/)) {399state.soyState.push("map-value")400state.soyState.push("record-key")401return null;402}403stream.next()404return null;405
406case "map-literal":407if (stream.match(/^[)]/)) {408state.soyState.pop();409return null;410}411if (stream.match(/[(,]/)) {412state.soyState.push("map-value")413state.soyState.push("map-value")414return null;415}416stream.next()417return null;418
419case "list-literal":420if (stream.match(']')) {421state.soyState.pop();422state.lookupVariables = true;423popcontext(state);424return null;425}426if (stream.match(/\bfor\b/)) {427state.lookupVariables = true;428state.soyState.push('for-loop');429return "keyword";430}431return expression(stream, state);432
433case "record-key":434if (stream.match(/[\w]+/)) {435return "property";436}437if (stream.match(/^[:]/)) {438state.soyState.pop();439return null;440}441stream.next();442return null;443
444case "map-value":445if (stream.peek() == ")" || stream.peek() == "," || stream.match(/^[:)]/)) {446state.soyState.pop();447return null;448}449return expression(stream, state);450
451case "import":452if (stream.eat(";")) {453state.soyState.pop();454state.indent -= 2 * config.indentUnit;455return null;456}457if (stream.match(/\w+(?=\s+as\b)/)) {458return "variable";459}460if (match = stream.match(/\w+/)) {461return /\b(from|as)\b/.test(match[0]) ? "keyword" : "def";462}463if (match = stream.match(/^["']/)) {464state.soyState.push("string");465state.quoteKind = match[0];466return "string";467}468stream.next();469return null;470
471case "tag":472var endTag;473var tagName;474if (state.tag === undefined) {475endTag = true;476tagName = '';477} else {478endTag = state.tag[0] == "/";479tagName = endTag ? state.tag.substring(1) : state.tag;480}481var tag = tags[tagName];482if (stream.match(/^\/?}/)) {483var selfClosed = stream.current() == "/}";484if (selfClosed && !endTag) {485popcontext(state);486}487if (state.tag == "/template" || state.tag == "/deltemplate") {488state.variables = prepend(null, 'ij');489state.indent = 0;490} else {491state.indent -= config.indentUnit *492(selfClosed || indentingTags.indexOf(state.tag) == -1 ? 2 : 1);493}494state.soyState.pop();495return "keyword";496} else if (stream.match(/^([\w?]+)(?==)/)) {497if (state.context && state.context.tag == tagName && stream.current() == "kind" && (match = stream.match(/^="([^"]+)/, false))) {498var kind = match[1];499state.context.kind = kind;500var mode = modes[kind] || modes.html;501var localState = last(state.localStates);502if (localState.mode.indent) {503state.indent += localState.mode.indent(localState.state, "", "");504}505state.localStates.push({506mode: mode,507state: CodeMirror.startState(mode)508});509}510return "attribute";511}512return expression(stream, state);513
514case "template-call-expression":515if (stream.match(/^([\w-?]+)(?==)/)) {516return "attribute";517} else if (stream.eat('>')) {518state.soyState.pop();519return "keyword";520} else if (stream.eat('/>')) {521state.soyState.pop();522return "keyword";523}524return expression(stream, state);525case "literal":526if (stream.match('{/literal}', false)) {527state.soyState.pop();528return this.token(stream, state);529}530return tokenUntil(stream, state, /\{\/literal}/);531case "export":532if (match = stream.match(/\w+/)) {533state.soyState.pop();534if (match == "const") {535state.soyState.push("const-def")536return "keyword";537} else if (match == "extern") {538state.soyState.push("param-def")539return "keyword";540}541} else {542stream.next();543}544return null;545case "const-def":546if (stream.match(/^\w+/)) {547state.soyState.pop();548return "def";549}550stream.next();551return null;552}553
554if (stream.match('{literal}')) {555state.indent += config.indentUnit;556state.soyState.push("literal");557state.context = new Context(state.context, "literal", state.variables);558return "keyword";559
560// A tag-keyword must be followed by whitespace, comment or a closing tag.561} else if (match = stream.match(/^\{([/@\\]?\w+\??)(?=$|[\s}]|\/[/*])/)) {562var prevTag = state.tag;563state.tag = match[1];564var endTag = state.tag[0] == "/";565var indentingTag = !!tags[state.tag];566var tagName = endTag ? state.tag.substring(1) : state.tag;567var tag = tags[tagName];568if (state.tag != "/switch")569state.indent += ((endTag || tag && tag.reduceIndent) && prevTag != "switch" ? 1 : 2) * config.indentUnit;570
571state.soyState.push("tag");572var tagError = false;573if (tag) {574if (!endTag) {575if (tag.soyState) state.soyState.push(tag.soyState);576}577// If a new tag, open a new context.578if (!tag.noEndTag && (indentingTag || !endTag)) {579state.context = new Context(state.context, state.tag, tag.variableScope ? state.variables : null);580// Otherwise close the current context.581} else if (endTag) {582var isBalancedForExtern = tagName == 'extern' && (state.context && state.context.tag == 'export');583if (!state.context || ((state.context.tag != tagName) && !isBalancedForExtern)) {584tagError = true;585} else if (state.context) {586if (state.context.kind) {587state.localStates.pop();588var localState = last(state.localStates);589if (localState.mode.indent) {590state.indent -= localState.mode.indent(localState.state, "", "");591}592}593popcontext(state);594}595}596} else if (endTag) {597// Assume all tags with a closing tag are defined in the config.598tagError = true;599}600return (tagError ? "error " : "") + "keyword";601
602// Not a tag-keyword; it's an implicit print tag.603} else if (stream.eat('{')) {604state.tag = "print";605state.indent += 2 * config.indentUnit;606state.soyState.push("tag");607return "keyword";608} else if (!state.context && stream.sol() && stream.match(/import\b/)) {609state.soyState.push("import");610state.indent += 2 * config.indentUnit;611return "keyword";612} else if (match = stream.match('<{')) {613state.soyState.push("template-call-expression");614state.indent += 2 * config.indentUnit;615state.soyState.push("tag");616return "keyword";617} else if (match = stream.match('</>')) {618state.indent -= 1 * config.indentUnit;619return "keyword";620}621
622return tokenUntil(stream, state, /\{|\s+\/\/|\/\*/);623},624
625indent: function(state, textAfter, line) {626var indent = state.indent, top = last(state.soyState);627if (top == "comment") return CodeMirror.Pass;628
629if (top == "literal") {630if (/^\{\/literal}/.test(textAfter)) indent -= config.indentUnit;631} else {632if (/^\s*\{\/(template|deltemplate)\b/.test(textAfter)) return 0;633if (/^\{(\/|(fallbackmsg|elseif|else|ifempty)\b)/.test(textAfter)) indent -= config.indentUnit;634if (state.tag != "switch" && /^\{(case|default)\b/.test(textAfter)) indent -= config.indentUnit;635if (/^\{\/switch\b/.test(textAfter)) indent -= config.indentUnit;636}637var localState = last(state.localStates);638if (indent && localState.mode.indent) {639indent += localState.mode.indent(localState.state, textAfter, line);640}641return indent;642},643
644innerMode: function(state) {645if (state.soyState.length && last(state.soyState) != "literal") return null;646else return last(state.localStates);647},648
649electricInput: /^\s*\{(\/|\/template|\/deltemplate|\/switch|fallbackmsg|elseif|else|case|default|ifempty|\/literal\})$/,650lineComment: "//",651blockCommentStart: "/*",652blockCommentEnd: "*/",653blockCommentContinue: " * ",654useInnerComments: false,655fold: "indent"656};657}, "htmlmixed");658
659CodeMirror.registerHelper("wordChars", "soy", /[\w$]/);660
661CodeMirror.registerHelper("hintWords", "soy", Object.keys(tags).concat(662["css", "debugger"]));663
664CodeMirror.defineMIME("text/x-soy", "soy");665});666