velocity.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: http://codemirror.net/LICENSE
  3. (function(mod) {
  4. if (typeof exports == "object" && typeof module == "object") // CommonJS
  5. mod(require("../../lib/codemirror"));
  6. else if (typeof define == "function" && define.amd) // AMD
  7. define(["../../lib/codemirror"], mod);
  8. else // Plain browser env
  9. mod(CodeMirror);
  10. })(function(CodeMirror) {
  11. "use strict";
  12. CodeMirror.defineMode("velocity", function() {
  13. function parseWords(str) {
  14. var obj = {}, words = str.split(" ");
  15. for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
  16. return obj;
  17. }
  18. var keywords = parseWords("#end #else #break #stop #[[ #]] " +
  19. "#{end} #{else} #{break} #{stop}");
  20. var functions = parseWords("#if #elseif #foreach #set #include #parse #macro #define #evaluate " +
  21. "#{if} #{elseif} #{foreach} #{set} #{include} #{parse} #{macro} #{define} #{evaluate}");
  22. var specials = parseWords("$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");
  23. var isOperatorChar = /[+\-*&%=<>!?:\/|]/;
  24. function chain(stream, state, f) {
  25. state.tokenize = f;
  26. return f(stream, state);
  27. }
  28. function tokenBase(stream, state) {
  29. var beforeParams = state.beforeParams;
  30. state.beforeParams = false;
  31. var ch = stream.next();
  32. // start of unparsed string?
  33. if ((ch == "'") && state.inParams) {
  34. state.lastTokenWasBuiltin = false;
  35. return chain(stream, state, tokenString(ch));
  36. }
  37. // start of parsed string?
  38. else if ((ch == '"')) {
  39. state.lastTokenWasBuiltin = false;
  40. if (state.inString) {
  41. state.inString = false;
  42. return "string";
  43. }
  44. else if (state.inParams)
  45. return chain(stream, state, tokenString(ch));
  46. }
  47. // is it one of the special signs []{}().,;? Seperator?
  48. else if (/[\[\]{}\(\),;\.]/.test(ch)) {
  49. if (ch == "(" && beforeParams)
  50. 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.eatWhile(/[\w\d\$_\.{}]/);
  82. // is it one of the specials?
  83. if (specials && specials.propertyIsEnumerable(stream.current())) {
  84. return "keyword";
  85. }
  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. }
  98. else {
  99. // get the whole word
  100. stream.eatWhile(/[\w\$_{}@]/);
  101. var word = stream.current();
  102. // is it one of the listed keywords?
  103. if (keywords && keywords.propertyIsEnumerable(word))
  104. return "keyword";
  105. // is it one of the listed functions?
  106. if (functions && functions.propertyIsEnumerable(word) ||
  107. (stream.current().match(/^#@?[a-z0-9_]+ *$/i) && stream.peek()=="(") &&
  108. !(functions && functions.propertyIsEnumerable(word.toLowerCase()))) {
  109. state.beforeParams = true;
  110. state.lastTokenWasBuiltin = false;
  111. return "keyword";
  112. }
  113. if (state.inString) {
  114. state.lastTokenWasBuiltin = false;
  115. return "string";
  116. }
  117. if (stream.pos > word.length && stream.string.charAt(stream.pos-word.length-1)=="." && state.lastTokenWasBuiltin)
  118. return "builtin";
  119. // default: just a "word"
  120. state.lastTokenWasBuiltin = false;
  121. return null;
  122. }
  123. }
  124. function tokenString(quote) {
  125. return function(stream, state) {
  126. var escaped = false, next, 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, ch;
  145. while (ch = stream.next()) {
  146. if (ch == "#" && maybeEnd) {
  147. state.tokenize = tokenBase;
  148. break;
  149. }
  150. maybeEnd = (ch == "*");
  151. }
  152. return "comment";
  153. }
  154. function tokenUnparsed(stream, state) {
  155. var maybeEnd = 0, ch;
  156. while (ch = stream.next()) {
  157. if (ch == "#" && maybeEnd == 2) {
  158. state.tokenize = tokenBase;
  159. break;
  160. }
  161. if (ch == "]")
  162. maybeEnd++;
  163. else if (ch != " ")
  164. 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. });