htmlmixed.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: https://codemirror.net/LICENSE
  3. ;(function (mod) {
  4. if (typeof exports == 'object' && typeof module == 'object')
  5. // CommonJS
  6. mod(require('../../lib/codemirror'), require('../xml/xml'), require('../javascript/javascript'), require('../css/css'))
  7. else if (typeof define == 'function' && define.amd)
  8. // AMD
  9. define(['../../lib/codemirror', '../xml/xml', '../javascript/javascript', '../css/css'], mod)
  10. // Plain browser env
  11. else mod(CodeMirror)
  12. })(function (CodeMirror) {
  13. 'use strict'
  14. var defaultTags = {
  15. script: [
  16. ['lang', /(javascript|babel)/i, 'javascript'],
  17. ['type', /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i, 'javascript'],
  18. ['type', /./, 'text/plain'],
  19. [null, null, 'javascript'],
  20. ],
  21. style: [
  22. ['lang', /^css$/i, 'css'],
  23. ['type', /^(text\/)?(x-)?(stylesheet|css)$/i, 'css'],
  24. ['type', /./, 'text/plain'],
  25. [null, null, 'css'],
  26. ],
  27. }
  28. function maybeBackup(stream, pat, style) {
  29. var cur = stream.current(),
  30. close = cur.search(pat)
  31. if (close > -1) {
  32. stream.backUp(cur.length - close)
  33. } else if (cur.match(/<\/?$/)) {
  34. stream.backUp(cur.length)
  35. if (!stream.match(pat, false)) stream.match(cur)
  36. }
  37. return style
  38. }
  39. var attrRegexpCache = {}
  40. function getAttrRegexp(attr) {
  41. var regexp = attrRegexpCache[attr]
  42. if (regexp) return regexp
  43. return (attrRegexpCache[attr] = new RegExp('\\s+' + attr + '\\s*=\\s*(\'|")?([^\'"]+)(\'|")?\\s*'))
  44. }
  45. function getAttrValue(text, attr) {
  46. var match = text.match(getAttrRegexp(attr))
  47. return match ? /^\s*(.*?)\s*$/.exec(match[2])[1] : ''
  48. }
  49. function getTagRegexp(tagName, anchored) {
  50. return new RegExp((anchored ? '^' : '') + '</\\s*' + tagName + '\\s*>', 'i')
  51. }
  52. function addTags(from, to) {
  53. for (var tag in from) {
  54. var dest = to[tag] || (to[tag] = [])
  55. var source = from[tag]
  56. for (var i = source.length - 1; i >= 0; i--) dest.unshift(source[i])
  57. }
  58. }
  59. function findMatchingMode(tagInfo, tagText) {
  60. for (var i = 0; i < tagInfo.length; i++) {
  61. var spec = tagInfo[i]
  62. if (!spec[0] || spec[1].test(getAttrValue(tagText, spec[0]))) return spec[2]
  63. }
  64. }
  65. CodeMirror.defineMode(
  66. 'htmlmixed',
  67. function (config, parserConfig) {
  68. var htmlMode = CodeMirror.getMode(config, {
  69. name: 'xml',
  70. htmlMode: true,
  71. multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
  72. multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag,
  73. allowMissingTagName: parserConfig.allowMissingTagName,
  74. })
  75. var tags = {}
  76. var configTags = parserConfig && parserConfig.tags,
  77. configScript = parserConfig && parserConfig.scriptTypes
  78. addTags(defaultTags, tags)
  79. if (configTags) addTags(configTags, tags)
  80. if (configScript) for (var i = configScript.length - 1; i >= 0; i--) tags.script.unshift(['type', configScript[i].matches, configScript[i].mode])
  81. function html(stream, state) {
  82. var style = htmlMode.token(stream, state.htmlState),
  83. tag = /\btag\b/.test(style),
  84. tagName
  85. if (tag && !/[<>\s\/]/.test(stream.current()) && (tagName = state.htmlState.tagName && state.htmlState.tagName.toLowerCase()) && tags.hasOwnProperty(tagName)) {
  86. state.inTag = tagName + ' '
  87. } else if (state.inTag && tag && />$/.test(stream.current())) {
  88. var inTag = /^([\S]+) (.*)/.exec(state.inTag)
  89. state.inTag = null
  90. var modeSpec = stream.current() == '>' && findMatchingMode(tags[inTag[1]], inTag[2])
  91. var mode = CodeMirror.getMode(config, modeSpec)
  92. var endTagA = getTagRegexp(inTag[1], true),
  93. endTag = getTagRegexp(inTag[1], false)
  94. state.token = function (stream, state) {
  95. if (stream.match(endTagA, false)) {
  96. state.token = html
  97. state.localState = state.localMode = null
  98. return null
  99. }
  100. return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState))
  101. }
  102. state.localMode = mode
  103. state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, '', ''))
  104. } else if (state.inTag) {
  105. state.inTag += stream.current()
  106. if (stream.eol()) state.inTag += ' '
  107. }
  108. return style
  109. }
  110. return {
  111. startState: function () {
  112. var state = CodeMirror.startState(htmlMode)
  113. return { token: html, inTag: null, localMode: null, localState: null, htmlState: state }
  114. },
  115. copyState: function (state) {
  116. var local
  117. if (state.localState) {
  118. local = CodeMirror.copyState(state.localMode, state.localState)
  119. }
  120. return { token: state.token, inTag: state.inTag, localMode: state.localMode, localState: local, htmlState: CodeMirror.copyState(htmlMode, state.htmlState) }
  121. },
  122. token: function (stream, state) {
  123. return state.token(stream, state)
  124. },
  125. indent: function (state, textAfter, line) {
  126. if (!state.localMode || /^\s*<\//.test(textAfter)) return htmlMode.indent(state.htmlState, textAfter, line)
  127. else if (state.localMode.indent) return state.localMode.indent(state.localState, textAfter, line)
  128. else return CodeMirror.Pass
  129. },
  130. innerMode: function (state) {
  131. return { state: state.localState || state.htmlState, mode: state.localMode || htmlMode }
  132. },
  133. }
  134. },
  135. 'xml',
  136. 'javascript',
  137. 'css'
  138. )
  139. CodeMirror.defineMIME('text/html', 'htmlmixed')
  140. })