jsx.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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'))
  7. else if (typeof define == 'function' && define.amd)
  8. // AMD
  9. define(['../../lib/codemirror', '../xml/xml', '../javascript/javascript'], mod)
  10. // Plain browser env
  11. else mod(CodeMirror)
  12. })(function (CodeMirror) {
  13. 'use strict'
  14. // Depth means the amount of open braces in JS context, in XML
  15. // context 0 means not in tag, 1 means in tag, and 2 means in tag
  16. // and js block comment.
  17. function Context(state, mode, depth, prev) {
  18. this.state = state
  19. this.mode = mode
  20. this.depth = depth
  21. this.prev = prev
  22. }
  23. function copyContext(context) {
  24. return new Context(CodeMirror.copyState(context.mode, context.state), context.mode, context.depth, context.prev && copyContext(context.prev))
  25. }
  26. CodeMirror.defineMode(
  27. 'jsx',
  28. function (config, modeConfig) {
  29. var xmlMode = CodeMirror.getMode(config, { name: 'xml', allowMissing: true, multilineTagIndentPastTag: false, allowMissingTagName: true })
  30. var jsMode = CodeMirror.getMode(config, (modeConfig && modeConfig.base) || 'javascript')
  31. function flatXMLIndent(state) {
  32. var tagName = state.tagName
  33. state.tagName = null
  34. var result = xmlMode.indent(state, '', '')
  35. state.tagName = tagName
  36. return result
  37. }
  38. function token(stream, state) {
  39. if (state.context.mode == xmlMode) return xmlToken(stream, state, state.context)
  40. else return jsToken(stream, state, state.context)
  41. }
  42. function xmlToken(stream, state, cx) {
  43. if (cx.depth == 2) {
  44. // Inside a JS /* */ comment
  45. if (stream.match(/^.*?\*\//)) cx.depth = 1
  46. else stream.skipToEnd()
  47. return 'comment'
  48. }
  49. if (stream.peek() == '{') {
  50. xmlMode.skipAttribute(cx.state)
  51. var indent = flatXMLIndent(cx.state),
  52. xmlContext = cx.state.context
  53. // If JS starts on same line as tag
  54. if (xmlContext && stream.match(/^[^>]*>\s*$/, false)) {
  55. while (xmlContext.prev && !xmlContext.startOfLine) xmlContext = xmlContext.prev
  56. // If tag starts the line, use XML indentation level
  57. if (xmlContext.startOfLine) indent -= config.indentUnit
  58. // Else use JS indentation level
  59. else if (cx.prev.state.lexical) indent = cx.prev.state.lexical.indented
  60. // Else if inside of tag
  61. } else if (cx.depth == 1) {
  62. indent += config.indentUnit
  63. }
  64. state.context = new Context(CodeMirror.startState(jsMode, indent), jsMode, 0, state.context)
  65. return null
  66. }
  67. if (cx.depth == 1) {
  68. // Inside of tag
  69. if (stream.peek() == '<') {
  70. // Tag inside of tag
  71. xmlMode.skipAttribute(cx.state)
  72. state.context = new Context(CodeMirror.startState(xmlMode, flatXMLIndent(cx.state)), xmlMode, 0, state.context)
  73. return null
  74. } else if (stream.match('//')) {
  75. stream.skipToEnd()
  76. return 'comment'
  77. } else if (stream.match('/*')) {
  78. cx.depth = 2
  79. return token(stream, state)
  80. }
  81. }
  82. var style = xmlMode.token(stream, cx.state),
  83. cur = stream.current(),
  84. stop
  85. if (/\btag\b/.test(style)) {
  86. if (/>$/.test(cur)) {
  87. if (cx.state.context) cx.depth = 0
  88. else state.context = state.context.prev
  89. } else if (/^</.test(cur)) {
  90. cx.depth = 1
  91. }
  92. } else if (!style && (stop = cur.indexOf('{')) > -1) {
  93. stream.backUp(cur.length - stop)
  94. }
  95. return style
  96. }
  97. function jsToken(stream, state, cx) {
  98. if (stream.peek() == '<' && jsMode.expressionAllowed(stream, cx.state)) {
  99. state.context = new Context(CodeMirror.startState(xmlMode, jsMode.indent(cx.state, '', '')), xmlMode, 0, state.context)
  100. jsMode.skipExpression(cx.state)
  101. return null
  102. }
  103. var style = jsMode.token(stream, cx.state)
  104. if (!style && cx.depth != null) {
  105. var cur = stream.current()
  106. if (cur == '{') {
  107. cx.depth++
  108. } else if (cur == '}') {
  109. if (--cx.depth == 0) state.context = state.context.prev
  110. }
  111. }
  112. return style
  113. }
  114. return {
  115. startState: function () {
  116. return { context: new Context(CodeMirror.startState(jsMode), jsMode) }
  117. },
  118. copyState: function (state) {
  119. return { context: copyContext(state.context) }
  120. },
  121. token: token,
  122. indent: function (state, textAfter, fullLine) {
  123. return state.context.mode.indent(state.context.state, textAfter, fullLine)
  124. },
  125. innerMode: function (state) {
  126. return state.context
  127. },
  128. }
  129. },
  130. 'xml',
  131. 'javascript'
  132. )
  133. CodeMirror.defineMIME('text/jsx', 'jsx')
  134. CodeMirror.defineMIME('text/typescript-jsx', { name: 'jsx', base: { name: 'javascript', typescript: true } })
  135. })