You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

203 line
7.0KB

  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") // 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.inString && 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 []{}().,;? Separator?
  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.eat("!");
  82. stream.eatWhile(/[\w\d\$_\.{}-]/);
  83. // is it one of the specials?
  84. if (specials && specials.propertyIsEnumerable(stream.current())) {
  85. return "keyword";
  86. }
  87. else {
  88. state.lastTokenWasBuiltin = true;
  89. state.beforeParams = true;
  90. return "builtin";
  91. }
  92. }
  93. // is it a operator?
  94. else if (isOperatorChar.test(ch)) {
  95. state.lastTokenWasBuiltin = false;
  96. stream.eatWhile(isOperatorChar);
  97. return "operator";
  98. }
  99. else {
  100. // get the whole word
  101. stream.eatWhile(/[\w\$_{}@]/);
  102. var word = stream.current();
  103. // is it one of the listed keywords?
  104. if (keywords && keywords.propertyIsEnumerable(word))
  105. return "keyword";
  106. // is it one of the listed functions?
  107. if (functions && functions.propertyIsEnumerable(word) ||
  108. (stream.current().match(/^#@?[a-z0-9_]+ *$/i) && stream.peek()=="(") &&
  109. !(functions && functions.propertyIsEnumerable(word.toLowerCase()))) {
  110. state.beforeParams = true;
  111. state.lastTokenWasBuiltin = false;
  112. return "keyword";
  113. }
  114. if (state.inString) {
  115. state.lastTokenWasBuiltin = false;
  116. return "string";
  117. }
  118. if (stream.pos > word.length && stream.string.charAt(stream.pos-word.length-1)=="." && state.lastTokenWasBuiltin)
  119. return "builtin";
  120. // default: just a "word"
  121. state.lastTokenWasBuiltin = false;
  122. return null;
  123. }
  124. }
  125. function tokenString(quote) {
  126. return function(stream, state) {
  127. var escaped = false, next, end = false;
  128. while ((next = stream.next()) != null) {
  129. if ((next == quote) && !escaped) {
  130. end = true;
  131. break;
  132. }
  133. if (quote=='"' && stream.peek() == '$' && !escaped) {
  134. state.inString = true;
  135. end = true;
  136. break;
  137. }
  138. escaped = !escaped && next == "\\";
  139. }
  140. if (end) state.tokenize = tokenBase;
  141. return "string";
  142. };
  143. }
  144. function tokenComment(stream, state) {
  145. var maybeEnd = false, 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, ch;
  157. while (ch = stream.next()) {
  158. if (ch == "#" && maybeEnd == 2) {
  159. state.tokenize = tokenBase;
  160. break;
  161. }
  162. if (ch == "]")
  163. maybeEnd++;
  164. else if (ch != " ")
  165. maybeEnd = 0;
  166. }
  167. return "meta";
  168. }
  169. // Interface
  170. return {
  171. startState: function() {
  172. return {
  173. tokenize: tokenBase,
  174. beforeParams: false,
  175. inParams: false,
  176. inString: false,
  177. lastTokenWasBuiltin: false
  178. };
  179. },
  180. token: function(stream, state) {
  181. if (stream.eatSpace()) return null;
  182. return state.tokenize(stream, state);
  183. },
  184. blockCommentStart: "#*",
  185. blockCommentEnd: "*#",
  186. lineComment: "##",
  187. fold: "velocity"
  188. };
  189. });
  190. CodeMirror.defineMIME("text/velocity", "velocity");
  191. });