velocity.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  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'))
  7. else if (typeof define == 'function' && define.amd)
  8. // AMD
  9. define(['../../lib/codemirror'], mod)
  10. // Plain browser env
  11. else mod(CodeMirror)
  12. })(function (CodeMirror) {
  13. 'use strict'
  14. CodeMirror.defineMode('velocity', function () {
  15. function parseWords(str) {
  16. var obj = {},
  17. words = str.split(' ')
  18. for (var i = 0; i < words.length; ++i) obj[words[i]] = true
  19. return obj
  20. }
  21. var keywords = parseWords('#end #else #break #stop #[[ #]] ' + '#{end} #{else} #{break} #{stop}')
  22. var functions = parseWords('#if #elseif #foreach #set #include #parse #macro #define #evaluate ' + '#{if} #{elseif} #{foreach} #{set} #{include} #{parse} #{macro} #{define} #{evaluate}')
  23. var specials = parseWords(
  24. '$foreach.count $foreach.hasNext $foreach.first $foreach.last $foreach.topmost $foreach.parent.count $foreach.parent.hasNext $foreach.parent.first $foreach.parent.last $foreach.parent $velocityCount $!bodyContent $bodyContent'
  25. )
  26. var isOperatorChar = /[+\-*&%=<>!?:\/|]/
  27. function chain(stream, state, f) {
  28. state.tokenize = f
  29. return f(stream, state)
  30. }
  31. function tokenBase(stream, state) {
  32. var beforeParams = state.beforeParams
  33. state.beforeParams = false
  34. var ch = stream.next()
  35. // start of unparsed string?
  36. if (ch == "'" && !state.inString && state.inParams) {
  37. state.lastTokenWasBuiltin = false
  38. return chain(stream, state, tokenString(ch))
  39. }
  40. // start of parsed string?
  41. else if (ch == '"') {
  42. state.lastTokenWasBuiltin = false
  43. if (state.inString) {
  44. state.inString = false
  45. return 'string'
  46. } else if (state.inParams) return chain(stream, state, tokenString(ch))
  47. }
  48. // is it one of the special signs []{}().,;? Separator?
  49. else if (/[\[\]{}\(\),;\.]/.test(ch)) {
  50. if (ch == '(' && beforeParams) state.inParams = true
  51. else if (ch == ')') {
  52. state.inParams = false
  53. state.lastTokenWasBuiltin = true
  54. }
  55. return null
  56. }
  57. // start of a number value?
  58. else if (/\d/.test(ch)) {
  59. state.lastTokenWasBuiltin = false
  60. stream.eatWhile(/[\w\.]/)
  61. return 'number'
  62. }
  63. // multi line comment?
  64. else if (ch == '#' && stream.eat('*')) {
  65. state.lastTokenWasBuiltin = false
  66. return chain(stream, state, tokenComment)
  67. }
  68. // unparsed content?
  69. else if (ch == '#' && stream.match(/ *\[ *\[/)) {
  70. state.lastTokenWasBuiltin = false
  71. return chain(stream, state, tokenUnparsed)
  72. }
  73. // single line comment?
  74. else if (ch == '#' && stream.eat('#')) {
  75. state.lastTokenWasBuiltin = false
  76. stream.skipToEnd()
  77. return 'comment'
  78. }
  79. // variable?
  80. else if (ch == '$') {
  81. stream.eat('!')
  82. stream.eatWhile(/[\w\d\$_\.{}-]/)
  83. // is it one of the specials?
  84. if (specials && specials.propertyIsEnumerable(stream.current())) {
  85. return 'keyword'
  86. } else {
  87. state.lastTokenWasBuiltin = true
  88. state.beforeParams = true
  89. return 'builtin'
  90. }
  91. }
  92. // is it a operator?
  93. else if (isOperatorChar.test(ch)) {
  94. state.lastTokenWasBuiltin = false
  95. stream.eatWhile(isOperatorChar)
  96. return 'operator'
  97. } else {
  98. // get the whole word
  99. stream.eatWhile(/[\w\$_{}@]/)
  100. var word = stream.current()
  101. // is it one of the listed keywords?
  102. if (keywords && keywords.propertyIsEnumerable(word)) return 'keyword'
  103. // is it one of the listed functions?
  104. if (
  105. (functions && functions.propertyIsEnumerable(word)) ||
  106. (stream.current().match(/^#@?[a-z0-9_]+ *$/i) && stream.peek() == '(' && !(functions && functions.propertyIsEnumerable(word.toLowerCase())))
  107. ) {
  108. state.beforeParams = true
  109. state.lastTokenWasBuiltin = false
  110. return 'keyword'
  111. }
  112. if (state.inString) {
  113. state.lastTokenWasBuiltin = false
  114. return 'string'
  115. }
  116. if (stream.pos > word.length && stream.string.charAt(stream.pos - word.length - 1) == '.' && state.lastTokenWasBuiltin) return 'builtin'
  117. // default: just a "word"
  118. state.lastTokenWasBuiltin = false
  119. return null
  120. }
  121. }
  122. function tokenString(quote) {
  123. return function (stream, state) {
  124. var escaped = false,
  125. next,
  126. end = false
  127. while ((next = stream.next()) != null) {
  128. if (next == quote && !escaped) {
  129. end = true
  130. break
  131. }
  132. if (quote == '"' && stream.peek() == '$' && !escaped) {
  133. state.inString = true
  134. end = true
  135. break
  136. }
  137. escaped = !escaped && next == '\\'
  138. }
  139. if (end) state.tokenize = tokenBase
  140. return 'string'
  141. }
  142. }
  143. function tokenComment(stream, state) {
  144. var maybeEnd = false,
  145. ch
  146. while ((ch = stream.next())) {
  147. if (ch == '#' && maybeEnd) {
  148. state.tokenize = tokenBase
  149. break
  150. }
  151. maybeEnd = ch == '*'
  152. }
  153. return 'comment'
  154. }
  155. function tokenUnparsed(stream, state) {
  156. var maybeEnd = 0,
  157. ch
  158. while ((ch = stream.next())) {
  159. if (ch == '#' && maybeEnd == 2) {
  160. state.tokenize = tokenBase
  161. break
  162. }
  163. if (ch == ']') maybeEnd++
  164. else if (ch != ' ') maybeEnd = 0
  165. }
  166. return 'meta'
  167. }
  168. // Interface
  169. return {
  170. startState: function () {
  171. return {
  172. tokenize: tokenBase,
  173. beforeParams: false,
  174. inParams: false,
  175. inString: false,
  176. lastTokenWasBuiltin: false,
  177. }
  178. },
  179. token: function (stream, state) {
  180. if (stream.eatSpace()) return null
  181. return state.tokenize(stream, state)
  182. },
  183. blockCommentStart: '#*',
  184. blockCommentEnd: '*#',
  185. lineComment: '##',
  186. fold: 'velocity',
  187. }
  188. })
  189. CodeMirror.defineMIME('text/velocity', 'velocity')
  190. })