smarty.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: https://codemirror.net/LICENSE
  3. /**
  4. * Smarty 2 and 3 mode.
  5. */
  6. ;(function (mod) {
  7. if (typeof exports == 'object' && typeof module == 'object')
  8. // CommonJS
  9. mod(require('../../lib/codemirror'))
  10. else if (typeof define == 'function' && define.amd)
  11. // AMD
  12. define(['../../lib/codemirror'], mod)
  13. // Plain browser env
  14. else mod(CodeMirror)
  15. })(function (CodeMirror) {
  16. 'use strict'
  17. CodeMirror.defineMode('smarty', function (config, parserConf) {
  18. var rightDelimiter = parserConf.rightDelimiter || '}'
  19. var leftDelimiter = parserConf.leftDelimiter || '{'
  20. var version = parserConf.version || 2
  21. var baseMode = CodeMirror.getMode(config, parserConf.baseMode || 'null')
  22. var keyFunctions = ['debug', 'extends', 'function', 'include', 'literal']
  23. var regs = {
  24. operatorChars: /[+\-*&%=<>!?]/,
  25. validIdentifier: /[a-zA-Z0-9_]/,
  26. stringChar: /['"]/,
  27. }
  28. var last
  29. function cont(style, lastType) {
  30. last = lastType
  31. return style
  32. }
  33. function chain(stream, state, parser) {
  34. state.tokenize = parser
  35. return parser(stream, state)
  36. }
  37. // Smarty 3 allows { and } surrounded by whitespace to NOT slip into Smarty mode
  38. function doesNotCount(stream, pos) {
  39. if (pos == null) pos = stream.pos
  40. return version === 3 && leftDelimiter == '{' && (pos == stream.string.length || /\s/.test(stream.string.charAt(pos)))
  41. }
  42. function tokenTop(stream, state) {
  43. var string = stream.string
  44. for (var scan = stream.pos; ; ) {
  45. var nextMatch = string.indexOf(leftDelimiter, scan)
  46. scan = nextMatch + leftDelimiter.length
  47. if (nextMatch == -1 || !doesNotCount(stream, nextMatch + leftDelimiter.length)) break
  48. }
  49. if (nextMatch == stream.pos) {
  50. stream.match(leftDelimiter)
  51. if (stream.eat('*')) {
  52. return chain(stream, state, tokenBlock('comment', '*' + rightDelimiter))
  53. } else {
  54. state.depth++
  55. state.tokenize = tokenSmarty
  56. last = 'startTag'
  57. return 'tag'
  58. }
  59. }
  60. if (nextMatch > -1) stream.string = string.slice(0, nextMatch)
  61. var token = baseMode.token(stream, state.base)
  62. if (nextMatch > -1) stream.string = string
  63. return token
  64. }
  65. // parsing Smarty content
  66. function tokenSmarty(stream, state) {
  67. if (stream.match(rightDelimiter, true)) {
  68. if (version === 3) {
  69. state.depth--
  70. if (state.depth <= 0) {
  71. state.tokenize = tokenTop
  72. }
  73. } else {
  74. state.tokenize = tokenTop
  75. }
  76. return cont('tag', null)
  77. }
  78. if (stream.match(leftDelimiter, true)) {
  79. state.depth++
  80. return cont('tag', 'startTag')
  81. }
  82. var ch = stream.next()
  83. if (ch == '$') {
  84. stream.eatWhile(regs.validIdentifier)
  85. return cont('variable-2', 'variable')
  86. } else if (ch == '|') {
  87. return cont('operator', 'pipe')
  88. } else if (ch == '.') {
  89. return cont('operator', 'property')
  90. } else if (regs.stringChar.test(ch)) {
  91. state.tokenize = tokenAttribute(ch)
  92. return cont('string', 'string')
  93. } else if (regs.operatorChars.test(ch)) {
  94. stream.eatWhile(regs.operatorChars)
  95. return cont('operator', 'operator')
  96. } else if (ch == '[' || ch == ']') {
  97. return cont('bracket', 'bracket')
  98. } else if (ch == '(' || ch == ')') {
  99. return cont('bracket', 'operator')
  100. } else if (/\d/.test(ch)) {
  101. stream.eatWhile(/\d/)
  102. return cont('number', 'number')
  103. } else {
  104. if (state.last == 'variable') {
  105. if (ch == '@') {
  106. stream.eatWhile(regs.validIdentifier)
  107. return cont('property', 'property')
  108. } else if (ch == '|') {
  109. stream.eatWhile(regs.validIdentifier)
  110. return cont('qualifier', 'modifier')
  111. }
  112. } else if (state.last == 'pipe') {
  113. stream.eatWhile(regs.validIdentifier)
  114. return cont('qualifier', 'modifier')
  115. } else if (state.last == 'whitespace') {
  116. stream.eatWhile(regs.validIdentifier)
  117. return cont('attribute', 'modifier')
  118. }
  119. if (state.last == 'property') {
  120. stream.eatWhile(regs.validIdentifier)
  121. return cont('property', null)
  122. } else if (/\s/.test(ch)) {
  123. last = 'whitespace'
  124. return null
  125. }
  126. var str = ''
  127. if (ch != '/') {
  128. str += ch
  129. }
  130. var c = null
  131. while ((c = stream.eat(regs.validIdentifier))) {
  132. str += c
  133. }
  134. for (var i = 0, j = keyFunctions.length; i < j; i++) {
  135. if (keyFunctions[i] == str) {
  136. return cont('keyword', 'keyword')
  137. }
  138. }
  139. if (/\s/.test(ch)) {
  140. return null
  141. }
  142. return cont('tag', 'tag')
  143. }
  144. }
  145. function tokenAttribute(quote) {
  146. return function (stream, state) {
  147. var prevChar = null
  148. var currChar = null
  149. while (!stream.eol()) {
  150. currChar = stream.peek()
  151. if (stream.next() == quote && prevChar !== '\\') {
  152. state.tokenize = tokenSmarty
  153. break
  154. }
  155. prevChar = currChar
  156. }
  157. return 'string'
  158. }
  159. }
  160. function tokenBlock(style, terminator) {
  161. return function (stream, state) {
  162. while (!stream.eol()) {
  163. if (stream.match(terminator)) {
  164. state.tokenize = tokenTop
  165. break
  166. }
  167. stream.next()
  168. }
  169. return style
  170. }
  171. }
  172. return {
  173. startState: function () {
  174. return {
  175. base: CodeMirror.startState(baseMode),
  176. tokenize: tokenTop,
  177. last: null,
  178. depth: 0,
  179. }
  180. },
  181. copyState: function (state) {
  182. return {
  183. base: CodeMirror.copyState(baseMode, state.base),
  184. tokenize: state.tokenize,
  185. last: state.last,
  186. depth: state.depth,
  187. }
  188. },
  189. innerMode: function (state) {
  190. if (state.tokenize == tokenTop) return { mode: baseMode, state: state.base }
  191. },
  192. token: function (stream, state) {
  193. var style = state.tokenize(stream, state)
  194. state.last = last
  195. return style
  196. },
  197. indent: function (state, text, line) {
  198. if (state.tokenize == tokenTop && baseMode.indent) return baseMode.indent(state.base, text, line)
  199. else return CodeMirror.Pass
  200. },
  201. blockCommentStart: leftDelimiter + '*',
  202. blockCommentEnd: '*' + rightDelimiter,
  203. }
  204. })
  205. CodeMirror.defineMIME('text/x-smarty', 'smarty')
  206. })