123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669 |
- // CodeMirror, copyright (c) by Marijn Haverbeke and others
- // Distributed under an MIT license: https://codemirror.net/LICENSE
- ;(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)
- // Plain browser env
- else mod(CodeMirror)
- })(function (CodeMirror) {
- 'use strict'
- var paramData = { noEndTag: true, soyState: 'param-def' }
- var tags = {
- alias: { noEndTag: true },
- delpackage: { noEndTag: true },
- namespace: { noEndTag: true, soyState: 'namespace-def' },
- '@attribute': paramData,
- '@attribute?': paramData,
- '@param': paramData,
- '@param?': paramData,
- '@inject': paramData,
- '@inject?': paramData,
- '@state': paramData,
- template: { soyState: 'templ-def', variableScope: true },
- extern: { soyState: 'param-def' },
- export: { soyState: 'export' },
- literal: {},
- msg: {},
- fallbackmsg: { noEndTag: true, reduceIndent: true },
- select: {},
- plural: {},
- let: { soyState: 'var-def' },
- if: {},
- javaimpl: {},
- jsimpl: {},
- elseif: { noEndTag: true, reduceIndent: true },
- else: { noEndTag: true, reduceIndent: true },
- switch: {},
- case: { noEndTag: true, reduceIndent: true },
- default: { noEndTag: true, reduceIndent: true },
- foreach: { variableScope: true, soyState: 'for-loop' },
- ifempty: { noEndTag: true, reduceIndent: true },
- for: { variableScope: true, soyState: 'for-loop' },
- 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 },
- velog: {},
- const: { soyState: 'const-def' },
- }
- var indentingTags = Object.keys(tags).filter(function (tag) {
- return !tags[tag].noEndTag || tags[tag].reduceIndent
- })
- CodeMirror.defineMode(
- 'soy',
- function (config) {
- var textMode = CodeMirror.getMode(config, 'text/plain')
- var modes = {
- html: CodeMirror.getMode(config, { name: 'text/html', multilineTagIndentFactor: 2, multilineTagIndentPastTag: false, allowMissingTagName: true }),
- attributes: textMode,
- text: textMode,
- uri: textMode,
- trusted_resource_uri: textMode,
- 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,
- }
- }
- function popcontext(state) {
- if (!state.context) return
- if (state.context.scope) {
- state.variables = state.context.scope
- }
- state.context = state.context.previousContext
- }
- // 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'
- }
- // Data for an open soy tag.
- function Context(previousContext, tag, scope) {
- this.previousContext = previousContext
- this.tag = tag
- this.kind = null
- this.scope = scope
- }
- 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(/\bmap(?=\()/)) {
- state.soyState.push('map-literal')
- return 'keyword'
- } else if (stream.match(/\brecord(?=\()/)) {
- 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
- }
- return {
- startState: function () {
- return {
- soyState: [],
- variables: prepend(null, 'ij'),
- scopes: null,
- indent: 0,
- quoteKind: null,
- context: null,
- lookupVariables: true, // Is unknown variables considered an error
- 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,
- context: state.context,
- indent: state.indent, // Indentation of the following line.
- quoteKind: state.quoteKind,
- lookupVariables: state.lookupVariables,
- 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()
- }
- if (!state.context || !state.context.scope) {
- 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'
- }
- 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'
- }
- }
- 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':
- if ((match = stream.match(/(\.?[a-zA-Z_][a-zA-Z_0-9]+)+/))) {
- state.soyState.pop()
- // If the first character is '.', it can only be a local template.
- if (match[0][0] == '.') {
- return 'variable-2'
- }
- // Otherwise
- return 'variable'
- }
- if ((match = stream.match(/^\$([\w]+)/))) {
- state.soyState.pop()
- return ref(state.variables, match[1], !state.lookupVariables)
- }
- stream.next()
- return null
- case 'namespace-def':
- if ((match = stream.match(/^\.?([\w\.]+)/))) {
- state.soyState.pop()
- return 'variable'
- }
- stream.next()
- return null
- case 'param-def':
- if ((match = stream.match(/^\*/))) {
- state.soyState.pop()
- state.soyState.push('param-type')
- return 'type'
- }
- 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
- case 'param-ref':
- if ((match = stream.match(/^\w+/))) {
- state.soyState.pop()
- return 'property'
- }
- stream.next()
- return null
- case 'open-parentheses':
- if (stream.match(/[)]/)) {
- state.soyState.pop()
- return null
- }
- 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]+|[?])/))) {
- return 'type'
- }
- stream.next()
- return null
- 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
- case 'var-def':
- if ((match = stream.match(/^\$([\w]+)/))) {
- state.variables = prepend(state.variables, match[1])
- state.soyState.pop()
- return 'def'
- }
- stream.next()
- return null
- 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':
- if (stream.match(']')) {
- 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\b)/)) {
- return 'variable'
- }
- if ((match = stream.match(/\w+/))) {
- return /\b(from|as)\b/.test(match[0]) ? 'keyword' : 'def'
- }
- if ((match = stream.match(/^["']/))) {
- state.soyState.push('string')
- state.quoteKind = match[0]
- return 'string'
- }
- stream.next()
- return null
- case 'tag':
- var endTag
- var tagName
- if (state.tag === undefined) {
- endTag = true
- tagName = ''
- } else {
- endTag = state.tag[0] == '/'
- tagName = endTag ? state.tag.substring(1) : state.tag
- }
- var tag = tags[tagName]
- if (stream.match(/^\/?}/)) {
- var selfClosed = stream.current() == '/}'
- if (selfClosed && !endTag) {
- popcontext(state)
- }
- if (state.tag == '/template' || state.tag == '/deltemplate') {
- state.variables = prepend(null, 'ij')
- state.indent = 0
- } else {
- state.indent -= config.indentUnit * (selfClosed || indentingTags.indexOf(state.tag) == -1 ? 2 : 1)
- }
- state.soyState.pop()
- return 'keyword'
- } else if (stream.match(/^([\w?]+)(?==)/)) {
- if (state.context && state.context.tag == tagName && stream.current() == 'kind' && (match = stream.match(/^="([^"]+)/, false))) {
- var kind = match[1]
- state.context.kind = kind
- var mode = modes[kind] || modes.html
- var localState = last(state.localStates)
- if (localState.mode.indent) {
- state.indent += localState.mode.indent(localState.state, '', '')
- }
- state.localStates.push({
- mode: mode,
- state: CodeMirror.startState(mode),
- })
- }
- return 'attribute'
- }
- return expression(stream, state)
- 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)
- case 'literal':
- if (stream.match('{/literal}', false)) {
- state.soyState.pop()
- return this.token(stream, state)
- }
- return tokenUntil(stream, state, /\{\/literal}/)
- case 'export':
- if ((match = stream.match(/\w+/))) {
- state.soyState.pop()
- if (match == 'const') {
- state.soyState.push('const-def')
- return 'keyword'
- } else if (match == 'extern') {
- state.soyState.push('param-def')
- return 'keyword'
- }
- } else {
- stream.next()
- }
- return null
- case 'const-def':
- if (stream.match(/^\w+/)) {
- state.soyState.pop()
- return 'def'
- }
- stream.next()
- return null
- }
- if (stream.match('{literal}')) {
- state.indent += config.indentUnit
- state.soyState.push('literal')
- state.context = new Context(state.context, 'literal', state.variables)
- return 'keyword'
- // A tag-keyword must be followed by whitespace, comment or a closing tag.
- } else if ((match = stream.match(/^\{([/@\\]?\w+\??)(?=$|[\s}]|\/[/*])/))) {
- var prevTag = state.tag
- state.tag = match[1]
- 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
- state.soyState.push('tag')
- var tagError = false
- if (tag) {
- if (!endTag) {
- if (tag.soyState) state.soyState.push(tag.soyState)
- }
- // 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) {
- var isBalancedForExtern = tagName == 'extern' && state.context && state.context.tag == 'export'
- if (!state.context || (state.context.tag != tagName && !isBalancedForExtern)) {
- 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
- }
- return (tagError ? 'error ' : '') + 'keyword'
- // 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'
- } else if (!state.context && stream.sol() && stream.match(/import\b/)) {
- state.soyState.push('import')
- state.indent += 2 * config.indentUnit
- return 'keyword'
- } 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'
- }
- return tokenUntil(stream, state, /\{|\s+\/\/|\/\*/)
- },
- indent: function (state, textAfter, line) {
- 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) {
- indent += localState.mode.indent(localState.state, textAfter, line)
- }
- 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'
- )
- CodeMirror.registerHelper('wordChars', 'soy', /[\w$]/)
- CodeMirror.registerHelper('hintWords', 'soy', Object.keys(tags).concat(['css', 'debugger']))
- CodeMirror.defineMIME('text/x-soy', 'soy')
- })
|